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.Arrays;
18  import java.util.Collection;
19  import java.util.List;
20  import java.util.concurrent.ConcurrentHashMap;
21  import java.util.concurrent.ConcurrentMap;
22  
23  import org.cometd.Bayeux;
24  import org.cometd.Channel;
25  import org.cometd.ChannelBayeuxListener;
26  import org.cometd.ChannelListener;
27  import org.cometd.Client;
28  import org.cometd.DataFilter;
29  import org.cometd.Message;
30  import org.cometd.SubscriptionListener;
31  import org.mortbay.log.Log;
32  import org.mortbay.util.LazyList;
33  
34  
35  /* ------------------------------------------------------------ */
36  /** A Bayuex Channel
37   * 
38   * @author gregw
39   *
40   */
41  public class ChannelImpl implements Channel
42  {
43      protected AbstractBayeux _bayeux;
44      private volatile ClientImpl[] _subscribers=new ClientImpl[0]; // copy on write
45      private volatile DataFilter[] _dataFilters=new DataFilter[0]; // copy on write
46      private volatile SubscriptionListener[] _subscriptionListeners=new SubscriptionListener[0]; // copy on write
47      private ChannelId _id;
48      private ConcurrentMap<String,ChannelImpl> _children = new ConcurrentHashMap<String, ChannelImpl>();
49      private ChannelImpl _wild;
50      private ChannelImpl _wildWild;
51      private boolean _persistent;
52      private int _split;
53      private boolean _lazy;
54      
55      /* ------------------------------------------------------------ */
56      ChannelImpl(String id,AbstractBayeux bayeux)
57      {
58          _id=new ChannelId(id);
59          _bayeux=bayeux;
60      }
61  
62      /* ------------------------------------------------------------ */
63      /**
64       * A Lazy channel marks published messages as lazy.
65       * Lazy messages are queued but do not wake up
66       * waiting clients.
67       * @return true if message is lazy
68       */
69      public boolean isLazy()
70      {
71          return _lazy;
72      }
73  
74      /* ------------------------------------------------------------ */
75      /**
76       * A Lazy channel marks published messages as lazy.
77       * Lazy messages are queued but do not wake up
78       * waiting clients.
79       * @param lazy true if message is lazy
80       */
81      public void setLazy(boolean lazy)
82      {
83          _lazy = lazy;
84      }
85      
86      /* ------------------------------------------------------------ */
87      public void addChild(ChannelImpl channel)
88      {
89          ChannelId child=channel.getChannelId();
90          if (!_id.isParentOf(child))
91          {
92              throw new IllegalArgumentException(_id+" not parent of "+child);
93          }
94  
95          String next = child.getSegment(_id.depth());
96  
97          if ((child.depth()-_id.depth())==1)
98          {
99              // synchronize add and doRemove to avoid concurrent updates
100             synchronized(this)
101             {
102                 // add the channel to this channels
103                 ChannelImpl old = _children.putIfAbsent(next,channel);
104                 if (old!=null)
105                     throw new IllegalArgumentException("Already Exists");
106 
107                 if (ChannelId.WILD.equals(next))
108                     _wild=channel;
109                 else if (ChannelId.WILDWILD.equals(next))
110                     _wildWild=channel;
111                 _bayeux.addChannel(channel);
112             }
113         }
114         else
115         {
116             // TODO is this branch needed?
117             ChannelImpl branch=(ChannelImpl)_bayeux.getChannel((_id.depth()==0?"/":(_id.toString()+"/"))+next,true);
118             branch.addChild(channel);
119         }
120 
121     }
122     
123     /* ------------------------------------------------------------ */
124     /**
125      * @param filter
126      */
127     public void addDataFilter(DataFilter filter)
128     {
129         synchronized(this)
130         {
131             _dataFilters=(DataFilter[])LazyList.addToArray(_dataFilters,filter,null);
132         }
133     }
134 
135     /* ------------------------------------------------------------ */
136     /* ------------------------------------------------------------ */
137     /**
138      * @return
139      */
140     public ChannelId getChannelId()
141     {
142         return _id;
143     }
144     
145     /* ------------------------------------------------------------ */
146     public ChannelImpl getChild(ChannelId id)
147     {
148         String next=id.getSegment(_id.depth());
149         if (next==null)
150             return null;
151         
152         ChannelImpl channel = _children.get(next);
153         
154         if (channel==null || channel.getChannelId().depth()==id.depth())
155         {
156             return channel;
157         }
158         return channel.getChild(id);
159     }
160 
161     /* ------------------------------------------------------------ */
162      public void getChannels(List<Channel> list)
163      {
164          synchronized(this)
165          {
166              list.add(this);
167              for (ChannelImpl channel: _children.values())
168                  channel.getChannels(list);
169          }
170      }
171 
172      /* ------------------------------------------------------------ */
173      public int getChannelCount()
174      {
175          return _children.size();
176      }
177      
178     /* ------------------------------------------------------------ */
179     /**
180      * @return
181      */
182     public String getId()
183     {
184         return _id.toString();
185     }
186 
187     
188     /* ------------------------------------------------------------ */
189     public boolean isPersistent()
190     {
191         return _persistent;
192     }
193 
194     /* ------------------------------------------------------------ */
195     public void deliver(Client from, Iterable<Client> to, Object data, String id)
196     {
197         MessageImpl message=_bayeux.newMessage();
198         message.put(Bayeux.CHANNEL_FIELD,getId());
199         message.put(Bayeux.DATA_FIELD,data);
200         if (id!=null)   
201             message.put(Bayeux.ID_FIELD,id);
202 
203         Message m=_bayeux.extendSendBayeux(from,message);
204         
205         if (m!=null)
206         {
207             for (Client t : to)
208                 ((ClientImpl)t).doDelivery(from,m);
209         }
210         if (m instanceof MessageImpl)
211             ((MessageImpl)m).decRef();
212     }
213     
214     /* ------------------------------------------------------------ */
215     public void publish(Client fromClient, Object data, String msgId)
216     {
217         _bayeux.doPublish(getChannelId(),fromClient,data,msgId,false);   
218     }
219 
220     /* ------------------------------------------------------------ */
221     public void publishLazy(Client fromClient, Object data, String msgId)
222     {
223         _bayeux.doPublish(getChannelId(),fromClient,data,msgId,true);   
224     }
225     
226     /* ------------------------------------------------------------ */
227     public boolean remove()
228     {
229         return _bayeux.removeChannel(this);
230     }
231     
232     /* ------------------------------------------------------------ */
233     public boolean doRemove(ChannelImpl channel, List<ChannelBayeuxListener> listeners)
234     {
235         ChannelId channelId = channel.getChannelId();
236         int diff = channel._id.depth()-_id.depth();
237         
238         if (diff>=1)
239         {
240             String key = channelId.getSegment(_id.depth());
241             ChannelImpl child = _children.get(key);
242             
243             if (child!=null)
244             {
245                 // is it this child we are removing?
246                 if (diff==1)
247                 {
248                     if (!child.isPersistent())
249                     {
250                         // synchronize add and doRemove to avoid concurrent updates
251                         synchronized(this)
252                         {
253                             if (child.getChannelCount()>0)
254                             {
255                                 // remove the children of the child
256                                 for (ChannelImpl c : child._children.values())
257                                     child.doRemove(c,listeners);
258                             }
259 
260                             // remove the child
261                             _children.remove(key);
262                         }
263                         for (ChannelBayeuxListener l : listeners)
264                             l.channelRemoved(channel);
265                         return true;
266                     }
267                     return false;
268                 }
269 
270                 boolean removed = child.doRemove(channel,listeners);
271                 if (removed && !child.isPersistent() && child.getChannelCount()==0 && child.getSubscriberCount()==0)
272                 {
273                     // synchronize add and doRemove to avoid concurrent updates
274                     synchronized(this)
275                     {
276                         _children.remove(key);
277                     }
278                     for (ChannelBayeuxListener l : listeners)
279                         l.channelRemoved(channel);
280                 }
281 
282                 return removed;
283             }
284 
285         }
286         return false;
287     }
288     
289     /* ------------------------------------------------------------ */
290     /* ------------------------------------------------------------ */
291     /**
292      * @param filter
293      */
294     public DataFilter removeDataFilter(DataFilter filter)
295     {
296         synchronized(this)
297         {
298             _dataFilters=(DataFilter[])LazyList.removeFromArray(_dataFilters,filter);
299             return filter;
300         }
301     }
302 
303     /* ------------------------------------------------------------ */
304     public void setPersistent(boolean persistent)
305     {
306         _persistent=persistent;
307     }
308 
309     /* ------------------------------------------------------------ */
310     /**
311      * @param client
312      */
313     public void subscribe(Client client)
314     {
315         if (!(client instanceof ClientImpl))
316             throw new IllegalArgumentException("Client instance not obtained from Bayeux.newClient()");
317         
318         synchronized (this)
319         {
320             for (ClientImpl c : _subscribers)
321             {
322                 if (client.equals(c))
323                     return;
324             }
325             _subscribers=(ClientImpl[])LazyList.addToArray(_subscribers,client,null);
326             
327             for (SubscriptionListener l : _subscriptionListeners)
328                 l.subscribed(client, this);
329         }
330         
331         ((ClientImpl)client).addSubscription(this);
332     }
333 
334     /* ------------------------------------------------------------ */
335     @Override
336     public String toString()
337     {
338         return _id.toString();
339     }
340 
341     /* ------------------------------------------------------------ */
342     /**
343      * @param client
344      */
345     public void unsubscribe(Client client)
346     {
347         if (!(client instanceof ClientImpl))
348             throw new IllegalArgumentException("Client instance not obtained from Bayeux.newClient()");
349         ((ClientImpl)client).removeSubscription(this);
350         synchronized(this)
351         {
352             _subscribers=(ClientImpl[])LazyList.removeFromArray(_subscribers,client);
353             
354             for (SubscriptionListener l : _subscriptionListeners)
355                 l.unsubscribed(client,this);
356             
357             if (!_persistent && _subscribers.length==0 && _children.size()==0)
358                 remove();
359         }
360     }
361 
362     /* ------------------------------------------------------------ */
363     protected void doDelivery(ChannelId to, Client from, Message msg)
364     {   
365         int tail = to.depth()-_id.depth();
366         
367         Object data = msg.getData();
368 	// if we have data, filter it
369 	if (data!=null)
370 	{
371 	    Object old = data;
372 	    
373 	    try
374 	    {
375 		switch(tail)
376 		{
377 		    case 0:      
378 		    {
379 			final DataFilter[] filters=_dataFilters;
380 			for (DataFilter filter: filters)
381 			{
382 			    data=filter.filter(from,this,data);
383 			    if (data==null)
384 				return;
385 			}
386 		    }
387 		    break;
388 
389 		    case 1:
390 			if (_wild!=null)  
391 			{
392 			    final DataFilter[] filters=_wild._dataFilters;
393 			    for (DataFilter filter: filters)
394 			    {
395 				data=filter.filter(from,this,data);
396 				if (data==null)
397 				    return;
398 			    }
399 			}
400 
401 		    default:
402 			if (_wildWild!=null)  
403 			{
404 			    final DataFilter[] filters=_wildWild._dataFilters;
405 			    for (DataFilter filter: filters)
406 			    {
407 				data=filter.filter(from,this,data);
408 				if (data==null)
409 				    return;
410 			    }
411 			}
412 		}
413 	    }
414 	    catch (IllegalStateException e)
415 	    {
416 		Log.debug(e);
417 		return;
418 	    }
419 
420 	    // TODO this may not be correct if the message is reused.
421 	    // probably should close message ?
422 	    if (data!=old)
423 		msg.put(AbstractBayeux.DATA_FIELD,data);
424 	}
425         
426         switch(tail)
427         {
428             case 0:
429             {
430                 if (_lazy && msg instanceof MessageImpl)
431                     ((MessageImpl)msg).setLazy(true);
432                 
433                 final ClientImpl[] subscribers=_subscribers;
434                 if (subscribers.length>0)
435                 {
436                     // fair delivery 
437                     int split=_split++%_subscribers.length;
438                     for (int i=split;i<subscribers.length;i++)
439                         subscribers[i].doDelivery(from,msg);
440                     for (int i=0;i<split;i++)
441                         subscribers[i].doDelivery(from,msg);
442                 }                
443                 break;
444             }
445 
446             case 1:
447                 if (_wild!=null)
448                 {
449                     if (_wild._lazy && msg instanceof MessageImpl)
450                         ((MessageImpl)msg).setLazy(true);
451                     final ClientImpl[] subscribers=_wild._subscribers;
452                     for (ClientImpl client: subscribers)
453                         client.doDelivery(from,msg);
454                 }
455 
456             default:
457             {
458                 if (_wildWild!=null)
459                 {
460                     if (_wildWild._lazy && msg instanceof MessageImpl)
461                         ((MessageImpl)msg).setLazy(true);
462                     final ClientImpl[] subscribers=_wildWild._subscribers;
463                     for (ClientImpl client: subscribers)
464                         client.doDelivery(from,msg);
465                 }
466                 String next = to.getSegment(_id.depth());
467                 ChannelImpl channel = _children.get(next);
468                 if (channel!=null)
469                     channel.doDelivery(to,from,msg);
470             }
471         }
472     }
473     
474     /* ------------------------------------------------------------ */
475     public Collection<Client> getSubscribers()
476     {
477         synchronized(this)
478         {
479             return Arrays.asList((Client[])_subscribers);
480         }
481     }
482 
483     /* ------------------------------------------------------------ */
484     public int getSubscriberCount()
485     {
486         synchronized(this)
487         {
488             return _subscribers.length;
489         }
490     }
491 
492 
493     /* ------------------------------------------------------------ */
494     /* (non-Javadoc)
495      * @see dojox.cometd.Channel#getFilters()
496      */
497     public Collection<DataFilter> getDataFilters()
498     {
499         synchronized(this)
500         {
501             return Arrays.asList(_dataFilters);
502         }
503     }
504 
505     /* ------------------------------------------------------------ */
506     public void addListener(ChannelListener listener)
507     {
508         synchronized(this)
509         {
510             if (listener instanceof SubscriptionListener)
511                 _subscriptionListeners=(SubscriptionListener[])LazyList.addToArray(_subscriptionListeners,listener,null);
512         }
513     }
514     
515 }