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.Collections;
19  import java.util.EventListener;
20  import java.util.List;
21  
22  import org.mortbay.util.LazyList;
23  
24  import dojox.cometd.Bayeux;
25  import dojox.cometd.Client;
26  import dojox.cometd.Extension;
27  import dojox.cometd.Listener;
28  import dojox.cometd.Message;
29  import dojox.cometd.MessageListener;
30  import dojox.cometd.RemoveListener;
31  
32  
33  /* ------------------------------------------------------------ */
34  /**
35   * 
36   * @author gregw
37   */
38  public class ClientImpl implements Client
39  {
40      private String _id;
41      private String _type;
42      private Object _messageQ=null;
43      private int _responsesPending;
44      private ChannelImpl[] _subscriptions=new ChannelImpl[0]; // copy on write
45      private boolean _JSONCommented;
46      private Listener _listener;
47      private RemoveListener[] _rListeners=new RemoveListener[0]; // copy on write
48      private MessageListener[] _syncMListeners=new MessageListener[0]; // copy on write
49      private MessageListener[] _asyncMListeners=new MessageListener[0]; // copy on write
50      protected AbstractBayeux _bayeux;
51      private String _browserId;
52      private int _adviseVersion;
53      private int _batch;
54      private int _maxQueue;
55      private long _timeout;
56  
57      /* ------------------------------------------------------------ */
58      protected ClientImpl(AbstractBayeux bayeux)
59      {
60          _bayeux=bayeux;
61          _maxQueue=-1;
62          _bayeux.addClient(this,null);
63          if (_bayeux.isLogInfo())
64              _bayeux.logInfo("newClient: "+this);
65      }
66      
67      /* ------------------------------------------------------------ */
68      protected ClientImpl(AbstractBayeux bayeux, String idPrefix)
69      {
70          _bayeux=bayeux;
71          _maxQueue=0;
72          
73          _bayeux.addClient(this,idPrefix);
74          
75          if (_bayeux.isLogInfo())
76              _bayeux.logInfo("newClient: "+this);
77          
78      }
79      
80      /* ------------------------------------------------------------ */
81      /**
82       * @deprecated
83       */
84      protected ClientImpl(AbstractBayeux bayeux, String idPrefix, Listener listener)
85      {
86          this(bayeux,idPrefix);
87          _listener=listener;
88      }
89      
90  
91      /* ------------------------------------------------------------ */
92      /**
93       * @deprecated use {@link Channel#publish(Client, Object, String)}
94       * @see dojox.cometd.Client#publish(java.lang.String, java.lang.Object, java.lang.String)
95       */
96      public void publish(String toChannel, Object data, String msgId)
97      {
98          _bayeux.getChannel(toChannel).publish(this,data,msgId);
99      }
100 
101     /* ------------------------------------------------------------ */
102     /** 
103      * @deprecated use {@link Channel#subscribe(Client)}
104      * @see dojox.cometd.Client#subscribe(java.lang.String)
105      */
106     public void subscribe(String toChannel)
107     {
108         _bayeux.subscribe(toChannel,this);
109     }
110 
111     /* ------------------------------------------------------------ */
112     /** 
113      * @deprecated use {@link Channel#unsubscribe(Client)}
114      * @see dojox.cometd.Client#unsubscribe(java.lang.String)
115      */
116     public void unsubscribe(String toChannel)
117     {
118         _bayeux.unsubscribe(toChannel,this);
119     }
120 
121     /* ------------------------------------------------------------ */
122     /**
123      * @deprecated use {@link #deliver(Client, String, Object, String)}
124      */
125     public void deliver(Client from, Message message)
126     {
127         for (Extension e:_bayeux._extensions)
128             message=e.send(message);
129         doDelivery(from,message);
130     }
131 
132     /* ------------------------------------------------------------ */
133     public void deliver(Client from, String toChannel, Object data, String id)
134     {
135         // TODO recycle maps
136         Message message=_bayeux.newMessage();
137         message.put(Bayeux.CHANNEL_FIELD,toChannel);
138         message.put(Bayeux.DATA_FIELD,data);
139         if (id!=null)   
140             message.put(Bayeux.ID_FIELD,id);
141 
142         for (Extension e:_bayeux._extensions)
143             message=e.send(message);
144         doDelivery(from,message);
145         
146         ((MessageImpl)message).decRef();
147     }
148     
149     /* ------------------------------------------------------------ */
150     protected void doDelivery(Client from, Message message)
151     {
152         MessageListener[] alisteners=null;
153         synchronized(this)
154         {
155             ((MessageImpl)message).incRef();
156             
157             if (_maxQueue<0)
158                 _messageQ=LazyList.add(_messageQ,message);
159             else if (_maxQueue>0)
160             { 
161                 if (LazyList.size(_messageQ)>=_maxQueue)
162                     _messageQ=LazyList.remove(_messageQ,0);
163                 _messageQ=LazyList.add(_messageQ,message);
164             }
165             
166             if (_batch==0 &&  _responsesPending<1)
167                 resume();
168 
169             // deliver unsynchronized
170             for (MessageListener l:_syncMListeners)
171                 l.deliver(from,this,message);
172             alisteners=_asyncMListeners;
173         }
174         
175         // deliver unsynchronized
176         for (MessageListener l:alisteners)
177             l.deliver(from,this,message);
178     }
179 
180 
181     /* ------------------------------------------------------------ */
182     public void startBatch()
183     {
184         synchronized(this)
185         {
186             _batch++;
187         }
188     }
189     
190     /* ------------------------------------------------------------ */
191     public void endBatch()
192     {
193         synchronized(this)
194         {
195             if (--_batch==0 && LazyList.size(_messageQ)>0 && _responsesPending<1)
196                 resume();
197         }
198     }
199     
200     /* ------------------------------------------------------------ */
201     public String getConnectionType()
202     {
203         return _type;
204     }
205     
206     /* ------------------------------------------------------------ */
207     /* (non-Javadoc)
208      * @see org.mortbay.cometd.C#getId()
209      */
210     public String getId()
211     {
212         return _id;
213     }
214    
215     /* ------------------------------------------------------------ */
216     /* (non-Javadoc)
217      * @see org.mortbay.cometd.C#hasMessages()
218      */
219     public boolean hasMessages()
220     {
221         synchronized(this)
222         {
223             return LazyList.size(_messageQ)>0;
224         }
225     }
226     
227     /* ------------------------------------------------------------ */
228     /**
229      * @return the commented
230      */
231     public boolean isJSONCommented()
232     {
233         synchronized(this)
234         {
235             return _JSONCommented;
236         }
237     }
238 
239     /* ------------------------------------------------------------ */
240     public boolean isLocal()
241     {
242         return true;
243     }
244        
245     /* ------------------------------------------------------------ */
246     /* ------------------------------------------------------------ */
247     /* (non-Javadoc)
248      * @see dojox.cometd.Client#remove(boolean)
249      */
250     public void remove(boolean timeout)
251     {
252         synchronized(this)
253         {
254             Client client=_bayeux.removeClient(_id);   
255             if (_bayeux.isLogInfo())
256                 _bayeux.logInfo("Remove client "+client+" timeout="+timeout); 
257             if (_rListeners!=null)
258                 for (RemoveListener l:_rListeners)
259                     l.removed(_id, timeout);
260             if (_browserId!=null)
261                 _bayeux.clientOffBrowser(getBrowserId(),_id);
262             _browserId=null;
263         }
264         resume();
265         
266     }
267     
268     /* ------------------------------------------------------------ */
269     public int responded()
270     {
271         synchronized(this)
272         {
273             return _responsesPending--;
274         }
275     }
276 
277     /* ------------------------------------------------------------ */
278     public int responsePending()
279     {
280         synchronized(this)
281         {
282             return ++_responsesPending;
283         }
284     }
285     
286     /* ------------------------------------------------------------ */
287     /** Called by deliver to resume anything waiting on this client.
288      */
289     public void resume()
290     {
291     }
292 
293     /* ------------------------------------------------------------ */
294     /**
295      * @param commented the commented to set
296      */
297     public void setJSONCommented(boolean commented)
298     {
299         synchronized(this)
300         {
301             _JSONCommented=commented;
302         }
303     }
304 
305     /* ------------------------------------------------------------ */
306     public void setListener(Listener listener)
307     {
308         synchronized(this)
309         {
310             if (_listener!=null)
311                 removeListener(_listener);
312             _listener=listener;
313             if (_listener!=null)
314                 addListener(_listener);
315         }
316     }
317 
318     /* ------------------------------------------------------------ */
319     public Listener getListener()
320     {
321         return _listener;
322     }
323 
324     /* ------------------------------------------------------------ */
325     /*
326      * @return the number of messages queued
327      */
328     public int getMessages()
329     {
330         synchronized(this)
331         {
332             return LazyList.size(_messageQ);
333         }
334     }
335     
336     /* ------------------------------------------------------------ */
337     /* (non-Javadoc)
338      * @see org.mortbay.cometd.C#takeMessages()
339      */
340     public List<Message> takeMessages()
341     {
342         synchronized(this)
343         {
344             switch (LazyList.size(_messageQ))
345             {
346                 case 0: return null;
347                 case 1: 
348                     Message message = (Message)LazyList.get(_messageQ,0);
349                     _messageQ=null;
350                     return Collections.singletonList(message);
351                 default:
352                     List<Message> messages = LazyList.getList(_messageQ);
353                     _messageQ=null;
354                     return messages;
355             }
356         }
357     }
358     
359 
360     /* ------------------------------------------------------------ */
361     public void returnMessages(List<Message> messages)
362     {
363         synchronized(this)
364         {
365             switch (LazyList.size(_messageQ))
366             {
367                 case 0:
368                     _messageQ=messages;
369                     break;
370                 case 1: 
371                 default:
372                     throw new IllegalStateException(); // TODO implement
373             }
374         }
375     }
376     
377     /* ------------------------------------------------------------ */
378     /* (non-Javadoc)
379      * @see org.mortbay.cometd.C#takeMessages()
380      */
381     public Message takeMessage()
382     {
383         synchronized(this)
384         {
385             switch (LazyList.size(_messageQ))
386             {
387                 case 0: return null;
388                 case 1: 
389                 {
390                     Message message = (Message)LazyList.get(_messageQ,0);
391                     _messageQ=null;
392                     return message;
393                 }
394                 default:
395                 {
396                     Message message = (Message)LazyList.get(_messageQ,0);
397                     _messageQ=LazyList.remove(_messageQ,0);
398                     return message;
399                 }
400             }
401         }
402     }
403     
404     /* ------------------------------------------------------------ */
405     public void returnMessage(Message message)
406     {
407         synchronized(this)
408         {
409             switch (LazyList.size(_messageQ))
410             {
411                 case 0:
412                     _messageQ=message;
413                     break;
414                 case 1: 
415                 default:
416                     throw new IllegalStateException(); // TODO implement
417             }
418         }
419     }
420     
421     /* ------------------------------------------------------------ */
422     public String toString()
423     {
424         return _id;
425     }
426 
427     /* ------------------------------------------------------------ */
428     protected void addSubscription(ChannelImpl channel)
429     {
430         synchronized (this)
431         {
432             _subscriptions=(ChannelImpl[])LazyList.addToArray(_subscriptions,channel,null);
433         }
434     }
435 
436     /* ------------------------------------------------------------ */
437     protected void removeSubscription(ChannelImpl channel)
438     {
439         synchronized (this)
440         {
441             _subscriptions=(ChannelImpl[])LazyList.removeFromArray(_subscriptions,channel);
442         }
443     }
444 
445     /* ------------------------------------------------------------ */
446     protected void setConnectionType(String type)
447     {
448         synchronized (this)
449         {
450             _type=type;
451         }
452     }
453 
454     /* ------------------------------------------------------------ */
455     protected void setId(String _id)
456     {
457         synchronized (this)
458         {
459             this._id=_id;
460         }
461     }
462 
463     /* ------------------------------------------------------------ */
464     protected void unsubscribeAll()
465     {
466         ChannelImpl[] subscriptions;
467         synchronized(this)
468         {
469             _messageQ=null;
470             subscriptions=_subscriptions;
471             _subscriptions=new ChannelImpl[0];
472         }
473         for (ChannelImpl channel : subscriptions)
474             channel.unsubscribe(this);
475         
476     }
477 
478     /* ------------------------------------------------------------ */
479     public void setBrowserId(String id)
480     {
481         if (_browserId!=null && !_browserId.equals(id))
482             _bayeux.clientOffBrowser(_browserId,_id);
483         _browserId=id;
484         if (_browserId!=null)
485             _bayeux.clientOnBrowser(_browserId,_id);
486     }
487 
488     /* ------------------------------------------------------------ */
489     public String getBrowserId()
490     {
491         return _browserId;
492     }
493     
494     /* ------------------------------------------------------------ */
495    public boolean equals(Object o)
496    {
497        if (!(o instanceof Client))
498            return false;
499        return getId().equals(((Client)o).getId());
500    }
501 
502    /* ------------------------------------------------------------ */
503    /**
504     * @return the advised
505     */
506    public int getAdviceVersion()
507    {
508        return _adviseVersion;
509    }
510 
511    /* ------------------------------------------------------------ */
512    /**
513     * @param advised the advised to set
514     */
515    public void setAdviceVersion(int version)
516    {
517        _adviseVersion=version;
518    }
519 
520    /* ------------------------------------------------------------ */
521    public void addListener(EventListener listener)
522    {
523        synchronized(this)
524        {
525            if (listener instanceof MessageListener)
526            {
527                if (listener instanceof MessageListener.Synchronous)
528                    _syncMListeners=(MessageListener[])LazyList.addToArray(_syncMListeners,listener,MessageListener.class);
529                else
530                    _asyncMListeners=(MessageListener[])LazyList.addToArray(_asyncMListeners,listener,MessageListener.class);
531            }
532                
533            if (listener instanceof RemoveListener)
534                _rListeners=(RemoveListener[])LazyList.addToArray(_rListeners,listener,RemoveListener.class);
535        }
536    }
537 
538    /* ------------------------------------------------------------ */
539    public void removeListener(EventListener listener)
540    {
541        synchronized(this)
542        {
543            if (listener instanceof MessageListener)
544            {
545                _syncMListeners=(MessageListener[])LazyList.removeFromArray(_syncMListeners,listener);
546                _asyncMListeners=(MessageListener[])LazyList.removeFromArray(_asyncMListeners,listener);
547            }
548            
549            if (listener instanceof RemoveListener)
550                _rListeners=(RemoveListener[])LazyList.removeFromArray(_rListeners,listener);
551        }
552    }
553 
554    /* ------------------------------------------------------------ */
555    public long getTimeout() 
556    {
557        return _timeout;
558    }
559    
560    /* ------------------------------------------------------------ */
561    public void setTimeout(long timeoutMS) 
562    {
563        _timeout=timeoutMS;
564    }
565 
566 }