View Javadoc

1   // ========================================================================
2   // Copyright 2006 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  //========================================================================
14  
15  package org.mortbay.cometd;
16  
17  import java.util.ArrayList;
18  import java.util.List;
19  import java.util.Queue;
20  
21  import org.cometd.Bayeux;
22  import org.cometd.Client;
23  import org.cometd.ClientListener;
24  import org.cometd.DeliverListener;
25  import org.cometd.Extension;
26  import org.cometd.Message;
27  import org.cometd.MessageListener;
28  import org.cometd.QueueListener;
29  import org.cometd.RemoveListener;
30  import org.mortbay.util.ArrayQueue;
31  import org.mortbay.util.LazyList;
32  import org.mortbay.util.ajax.JSON;
33  
34  
35  
36  /* ------------------------------------------------------------ */
37  /**
38   * 
39   * @author gregw
40   */
41  public class ClientImpl implements Client
42  {
43      private String _id;
44      private String _type;
45      private int _responsesPending;
46      private ChannelImpl[] _subscriptions=new ChannelImpl[0]; // copy on write
47      private RemoveListener[] _rListeners; // copy on write
48      private MessageListener[] _syncMListeners; // copy on write
49      private MessageListener[] _asyncMListeners; // copy on write
50      private QueueListener[] _qListeners; // copy on write
51      private DeliverListener[] _dListeners; // copy on write
52      protected AbstractBayeux _bayeux;
53      private String _browserId;
54      private JSON.Literal _advice;
55      private int _batch;
56      private int _maxQueue;
57      private ArrayQueue<Message> _queue=new ArrayQueue<Message>(8,16,this);
58      private long _timeout;
59      private int _lag;
60      private Extension[] _extensions;
61      
62      private boolean _deliverViaMetaConnectOnly;
63      private volatile boolean _isExpired;
64      
65      // manipulated and synchronized by AbstractBayeux
66      int _adviseVersion;
67  
68      /* ------------------------------------------------------------ */
69      protected ClientImpl(AbstractBayeux bayeux)
70      {
71          _bayeux=bayeux;
72          _maxQueue=bayeux.getMaxClientQueue();
73          _bayeux.addClient(this,null);
74          if (_bayeux.isLogInfo())
75              _bayeux.logInfo("newClient: "+this);
76      }
77      
78      /* ------------------------------------------------------------ */
79      protected ClientImpl(AbstractBayeux bayeux, String idPrefix)
80      {
81          _bayeux=bayeux;
82          _maxQueue=0;
83          
84          _bayeux.addClient(this,idPrefix);
85          
86          if (_bayeux.isLogInfo())
87              _bayeux.logInfo("newClient: "+this);
88      }
89  
90      /* ------------------------------------------------------------ */
91      public void addExtension(Extension ext)
92      {
93          _extensions = (Extension[])LazyList.addToArray(_extensions,ext,Extension.class);
94      }
95  
96      /* ------------------------------------------------------------ */
97      Extension[] getExtensions()
98      {
99          return _extensions;
100     }
101     
102     /* ------------------------------------------------------------ */
103     public void deliver(Client from, String toChannel, Object data, String id)
104     {
105         MessageImpl message=_bayeux.newMessage();
106         message.put(Bayeux.CHANNEL_FIELD,toChannel);
107         message.put(Bayeux.DATA_FIELD,data);
108         if (id!=null)   
109             message.put(Bayeux.ID_FIELD,id);
110 
111         Message m=_bayeux.extendSendBayeux(from,message);
112         if (m!=null)
113             doDelivery(from,m);
114         if (m instanceof MessageImpl)
115             ((MessageImpl)m).decRef();
116     }
117     
118     /* ------------------------------------------------------------ */
119     public void deliverLazy(Client from, String toChannel, Object data, String id)
120     {
121         MessageImpl message=_bayeux.newMessage();
122         message.put(Bayeux.CHANNEL_FIELD,toChannel);
123         message.put(Bayeux.DATA_FIELD,data);
124         if (id!=null)   
125             message.put(Bayeux.ID_FIELD,id);
126         message.setLazy(true);
127         Message m=_bayeux.extendSendBayeux(from,message);
128         if (m!=null)
129             doDelivery(from,m);
130         if (m instanceof MessageImpl)
131             ((MessageImpl)m).decRef();
132     }
133     
134     /* ------------------------------------------------------------ */
135     protected void doDelivery(Client from, final Message msg)
136     {
137         final Message message=_bayeux.extendSendClient(from,this,msg);
138         if (message==null)
139             return;
140         
141         MessageListener[] alisteners=null;
142         synchronized(this)
143         {
144             if (_maxQueue<0)
145             {
146                 // No queue limit, so always queue the message
147                 ((MessageImpl)message).incRef();
148                 _queue.addUnsafe(message);
149             }
150             else
151             {
152                 // We have a queue limit, 
153                 boolean queue;
154                 if (_queue.size()>=_maxQueue)
155                 {
156                     // We are over the limit, so consult listeners
157                     if (_qListeners!=null && _qListeners.length>0)
158                     {
159                         queue=true;
160                         for (QueueListener l : _qListeners)
161                             queue &= l.queueMaxed(from,this,message);
162                     }
163                     else
164                         queue=false;
165                 }
166                 else
167                     // we are under limit, so queue the messages.
168                     queue=true;
169                 
170                 // queue the message if we are meant to
171                 if (queue)
172                 {
173                     ((MessageImpl)message).incRef();
174                     _queue.addUnsafe(message);
175                 }
176             }    
177 
178             // deliver synchronized
179             if (_syncMListeners!=null)
180                 for (MessageListener l:_syncMListeners)
181                     l.deliver(from,this,message);
182             alisteners=_asyncMListeners;
183              
184             if (_batch==0 &&  _responsesPending<1 && _queue.size()>0 && !((MessageImpl)message).isLazy())
185                 resume();
186         }
187         
188         // deliver unsynchronized
189         if (alisteners!=null)
190             for (MessageListener l:alisteners)
191                 l.deliver(from,this,message);
192     }
193 
194     /* ------------------------------------------------------------ */
195     public void doDeliverListeners()
196     {
197         synchronized (this)
198         {
199             if (_dListeners!=null)
200                 for (DeliverListener l:_dListeners)
201                     l.deliver(this,_queue);
202         }
203     }
204 
205     /* ------------------------------------------------------------ */
206     public void setMetaConnectDeliveryOnly(boolean deliverViaMetaConnectOnly)
207     {
208         _deliverViaMetaConnectOnly = deliverViaMetaConnectOnly;
209     }
210     
211     /* ------------------------------------------------------------ */
212     public boolean isMetaConnectDeliveryOnly()
213     {
214         return _deliverViaMetaConnectOnly;
215     }
216     
217     /* ------------------------------------------------------------ */
218     public void startBatch()
219     {
220         synchronized(this)
221         {
222             _batch++;
223         }
224     }
225     
226     /* ------------------------------------------------------------ */
227     public void endBatch()
228     {
229         synchronized(this)
230         {
231             if (--_batch==0 && _responsesPending<1)
232             {
233                 switch(_queue.size())
234                 {
235                     case 0:
236                         break;
237                     case 1:
238                         if (!((MessageImpl)_queue.get(0)).isLazy())
239                             resume();
240                         break;
241                     default:
242                         // TODO check entire queue for non-lazy messages!
243                         resume();
244                 }
245             }
246         }
247     }
248     
249     /* ------------------------------------------------------------ */
250     public String getConnectionType()
251     {
252         return _type;
253     }
254     
255     /* ------------------------------------------------------------ */
256     /* (non-Javadoc)
257      * @see org.mortbay.cometd.C#getId()
258      */
259     public String getId()
260     {
261         return _id;
262     }
263    
264     /* ------------------------------------------------------------ */
265     public boolean hasMessages()
266     {
267         return _queue.size()>0;
268     }
269    
270     /* ------------------------------------------------------------ */
271     public boolean hasNonLazyMessages()
272     {
273         synchronized (this)
274         {
275             for (int i=_queue.size();i-->0;)
276             {
277                 if (!((MessageImpl)_queue.getUnsafe(i)).isLazy())
278                     return true;
279             }
280         }
281         return false;
282     }
283 
284     /* ------------------------------------------------------------ */
285     public boolean isLocal()
286     {
287         return true;
288     }
289 
290     /* ------------------------------------------------------------ */
291     /* (non-Javadoc)
292      * @see org.cometd.Client#disconnect()
293      */
294     public void disconnect()
295     {
296         synchronized(this)
297         {
298             if (_bayeux.hasClient(_id))
299                 remove(false);
300         }
301     }
302 
303     /* ------------------------------------------------------------ */
304     /* (non-Javadoc)
305      * @see dojox.cometd.Client#remove(boolean)
306      */
307     public void remove(boolean timeout)
308     {
309         _isExpired = timeout;
310         Client client=_bayeux.removeClient(_id); 
311         
312         if (client!=null && _bayeux.isLogInfo())
313             _bayeux.logInfo("Remove client "+client+" timeout="+timeout); 
314         
315         final String browser_id;
316         final RemoveListener[] listeners;
317         synchronized(this)
318         {  
319             browser_id=_browserId;
320             _browserId=null;
321             listeners=_rListeners;
322         }
323 
324         if (browser_id!=null)
325             _bayeux.clientOffBrowser(browser_id,_id);
326         if (listeners!=null)
327             for (RemoveListener l:listeners)
328                 l.removed(_id, timeout);
329         
330         resume();
331     }
332     
333     public boolean isExpired ()
334     {
335         return _isExpired;
336     }
337     
338     /* ------------------------------------------------------------ */
339     public int responded()
340     {
341         synchronized(this)
342         {
343             return _responsesPending--;
344         }
345     }
346 
347     /* ------------------------------------------------------------ */
348     public int responsePending()
349     {
350         synchronized(this)
351         {
352             return ++_responsesPending;
353         }
354     }
355     
356     /* ------------------------------------------------------------ */
357     /** Called by deliver to resume anything waiting on this client.
358      */
359     public void resume()
360     {
361     }
362     
363     /* ------------------------------------------------------------ */
364     /*
365      * @return the number of messages queued
366      */
367     public int getMessages()
368     {
369         return _queue.size();
370     }
371     
372     /* ------------------------------------------------------------ */
373     public List<Message> takeMessages()
374     {
375         synchronized(this)
376         {
377             ArrayList<Message> list = new ArrayList<Message>(_queue);
378             _queue.clear();
379             return list;
380         }
381     }
382     
383 
384     /* ------------------------------------------------------------ */
385     public void returnMessages(List<Message> messages)
386     {
387         synchronized(this)
388         {
389             _queue.addAll(0,messages);
390         }
391     }
392         
393     /* ------------------------------------------------------------ */
394     @Override
395     public String toString()
396     {
397         return _id;
398     }
399 
400     /* ------------------------------------------------------------ */
401     protected void addSubscription(ChannelImpl channel)
402     {
403         synchronized (this)
404         {
405             _subscriptions=(ChannelImpl[])LazyList.addToArray(_subscriptions,channel,null);
406         }
407     }
408 
409     /* ------------------------------------------------------------ */
410     protected void removeSubscription(ChannelImpl channel)
411     {
412         synchronized (this)
413         {
414             _subscriptions=(ChannelImpl[])LazyList.removeFromArray(_subscriptions,channel);
415         }
416     }
417 
418     /* ------------------------------------------------------------ */
419     protected void setConnectionType(String type)
420     {
421         synchronized (this)
422         {
423             _type=type;
424         }
425     }
426 
427     /* ------------------------------------------------------------ */
428     protected void setId(String id)
429     {
430         synchronized (this)
431         {
432             _id=id;
433         }
434     }
435 
436     /* ------------------------------------------------------------ */
437     protected void unsubscribeAll()
438     {
439         ChannelImpl[] subscriptions;
440         synchronized(this)
441         {
442             _queue.clear();
443             subscriptions=_subscriptions;
444             _subscriptions=new ChannelImpl[0];
445         }
446         for (ChannelImpl channel : subscriptions)
447             channel.unsubscribe(this);
448         
449     }
450 
451     /* ------------------------------------------------------------ */
452     public void setBrowserId(String id)
453     {
454         if (_browserId!=null && !_browserId.equals(id))
455             _bayeux.clientOffBrowser(_browserId,_id);
456         _browserId=id;
457         if (_browserId!=null)
458             _bayeux.clientOnBrowser(_browserId,_id);
459     }
460 
461     /* ------------------------------------------------------------ */
462     public String getBrowserId()
463     {
464         return _browserId;
465     }
466 
467     /* ------------------------------------------------------------ */
468     @Override
469     public boolean equals(Object o)
470     {
471     	if (!(o instanceof Client))
472     		return false;
473     	return getId().equals(((Client)o).getId());
474     }
475 
476     /* ------------------------------------------------------------ */
477     /**
478      * Get the advice specific for this Client
479      * @return advice specific for this client or null
480      */
481     public JSON.Literal getAdvice()
482     {
483     	return _advice;
484     }
485 
486     /* ------------------------------------------------------------ */
487     /**
488      * @param advice specific for this client
489      */
490     public void setAdvice(JSON.Literal advice)
491     {
492     	_advice=advice;
493     }
494     
495 
496     /* ------------------------------------------------------------ */
497     public void addListener(ClientListener listener)
498     {
499         synchronized(this)
500         {
501             if (listener instanceof MessageListener)
502             {
503                 if (listener instanceof MessageListener.Synchronous)
504                     _syncMListeners=(MessageListener[])LazyList.addToArray(_syncMListeners,listener,MessageListener.class);
505                 else
506                     _asyncMListeners=(MessageListener[])LazyList.addToArray(_asyncMListeners,listener,MessageListener.class);
507             }
508 
509             if (listener instanceof RemoveListener)
510                 _rListeners=(RemoveListener[])LazyList.addToArray(_rListeners,listener,RemoveListener.class);
511 
512             if (listener instanceof QueueListener)
513                 _qListeners=(QueueListener[])LazyList.addToArray(_qListeners,listener,QueueListener.class);
514 
515             if (listener instanceof DeliverListener)
516                 _dListeners=(DeliverListener[])LazyList.addToArray(_dListeners,listener,DeliverListener.class);
517         }
518     }
519 
520     /* ------------------------------------------------------------ */
521     public void removeListener(ClientListener listener)
522     {
523         synchronized(this)
524         {
525             if (listener instanceof MessageListener)
526             {
527                 _syncMListeners=(MessageListener[])LazyList.removeFromArray(_syncMListeners,listener);
528                 _asyncMListeners=(MessageListener[])LazyList.removeFromArray(_asyncMListeners,listener);
529             }
530 
531             if (listener instanceof RemoveListener)
532                 _rListeners=(RemoveListener[])LazyList.removeFromArray(_rListeners,listener);
533 
534             if (listener instanceof QueueListener)
535                 _qListeners=(QueueListener[])LazyList.removeFromArray(_qListeners,listener);
536         }
537     }
538 
539     /* ------------------------------------------------------------ */
540     public long getTimeout() 
541     {
542     	return _timeout;
543     }
544 
545     /* ------------------------------------------------------------ */
546     public void setTimeout(long timeoutMS) 
547     {
548     	_timeout=timeoutMS;
549     }
550 
551     /* ------------------------------------------------------------ */
552     public void setMaxQueue(int maxQueue)
553     {
554         _maxQueue=maxQueue;
555     }
556     
557     /* ------------------------------------------------------------ */
558     public int getMaxQueue()
559     {
560         return _maxQueue;
561     }
562     
563     /* ------------------------------------------------------------ */
564     public Queue<Message> getQueue()
565     {
566         return _queue;
567     }
568 
569     /* ------------------------------------------------------------ */
570     /**
571      * @see org.mortbay.cometd.ext.TimesyncExtension
572      * @return The lag in ms as measured by an extension like the TimesyncExtension
573      */
574     public int getLag()
575     {
576         return _lag;
577     }
578 
579     /* ------------------------------------------------------------ */
580     /**
581      * @see org.mortbay.cometd.ext.TimesyncExtension
582      * @param lag in ms
583      */
584     public void setLag(int lag)
585     {
586         _lag = lag;
587     }
588     
589     
590 }