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.io.IOException;
18  import java.security.SecureRandom;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.ListIterator;
25  import java.util.Map;
26  import java.util.Random;
27  import java.util.Set;
28  import java.util.concurrent.ConcurrentHashMap;
29  import java.util.concurrent.CopyOnWriteArrayList;
30  
31  import javax.servlet.ServletContext;
32  import javax.servlet.http.HttpServletRequest;
33  
34  import org.cometd.Bayeux;
35  import org.cometd.BayeuxListener;
36  import org.cometd.Channel;
37  import org.cometd.ChannelBayeuxListener;
38  import org.cometd.Client;
39  import org.cometd.Extension;
40  import org.cometd.Message;
41  import org.cometd.ClientBayeuxListener;
42  import org.cometd.SecurityPolicy;
43  import org.mortbay.util.ajax.JSON;
44  
45  
46  /* ------------------------------------------------------------ */
47  /**
48   * @author gregw
49   * @author aabeling: added JSONP transport
50   * 
51   */
52  public abstract class AbstractBayeux extends MessagePool implements Bayeux
53  {   
54      public static final ChannelId META_ID=new ChannelId(META);
55      public static final ChannelId META_CONNECT_ID=new ChannelId(META_CONNECT);
56      public static final ChannelId META_CLIENT_ID=new ChannelId(META_CLIENT);
57      public static final ChannelId META_DISCONNECT_ID=new ChannelId(META_DISCONNECT);
58      public static final ChannelId META_HANDSHAKE_ID=new ChannelId(META_HANDSHAKE);
59      public static final ChannelId META_PING_ID=new ChannelId(META_PING);
60      public static final ChannelId META_STATUS_ID=new ChannelId(META_STATUS);
61      public static final ChannelId META_SUBSCRIBE_ID=new ChannelId(META_SUBSCRIBE);
62      public static final ChannelId META_UNSUBSCRIBE_ID=new ChannelId(META_UNSUBSCRIBE);
63      
64  
65      private static final Map<String,Object> EXT_JSON_COMMENTED=new HashMap<String,Object>(2){
66          {
67              this.put("json-comment-filtered",Boolean.TRUE);
68          }
69      };
70      
71      
72      private HashMap<String,Handler> _handlers=new HashMap<String,Handler>();
73      
74      private ChannelImpl _root = new ChannelImpl("/",this);
75      private ConcurrentHashMap<String,ClientImpl> _clients=new ConcurrentHashMap<String,ClientImpl>();
76      protected SecurityPolicy _securityPolicy=new DefaultPolicy();
77      protected JSON.Literal _advice;
78      protected JSON.Literal _multiFrameAdvice; 
79      protected int _adviceVersion=0;
80      protected Object _handshakeAdvice=new JSON.Literal("{\"reconnect\":\"handshake\",\"interval\":500}");
81      protected int _logLevel;
82      protected long _timeout=240000;
83      protected long _interval=0;
84      protected long _maxInterval=30000;
85      protected boolean _JSONCommented;
86      protected boolean _initialized;
87      protected ConcurrentHashMap<String, List<String>> _browser2client=new ConcurrentHashMap<String, List<String>>();
88      protected int _multiFrameInterval=-1;
89      
90      protected boolean _directDeliver=true;
91      protected boolean _requestAvailable;
92      protected ThreadLocal<HttpServletRequest> _request = new ThreadLocal<HttpServletRequest>();
93      
94      transient ServletContext _context;
95      transient Random _random;
96      transient ConcurrentHashMap<String, ChannelId> _channelIdCache;
97      protected Handler _publishHandler;
98      protected Handler _metaPublishHandler;
99      protected int _maxClientQueue=-1;
100 
101     protected List<Extension> _extensions=new CopyOnWriteArrayList<Extension>();
102     protected JSON.Literal _transports=new JSON.Literal("[\""+Bayeux.TRANSPORT_LONG_POLL+ "\",\""+Bayeux.TRANSPORT_CALLBACK_POLL+"\"]");
103     protected List<ClientBayeuxListener> _clientListeners=new CopyOnWriteArrayList<ClientBayeuxListener>();
104     protected List<ChannelBayeuxListener> _channelListeners=new CopyOnWriteArrayList<ChannelBayeuxListener>();
105     
106     /* ------------------------------------------------------------ */
107     /**
108      * @param context.
109      *            The logLevel init parameter is used to set the logging to
110      *            0=none, 1=info, 2=debug
111      */
112     protected AbstractBayeux()
113     {
114         _publishHandler=new PublishHandler();
115         _metaPublishHandler=new MetaPublishHandler();
116         _handlers.put(META_HANDSHAKE,new HandshakeHandler());
117         _handlers.put(META_CONNECT,new ConnectHandler());
118         _handlers.put(META_DISCONNECT,new DisconnectHandler());
119         _handlers.put(META_SUBSCRIBE,new SubscribeHandler());
120         _handlers.put(META_UNSUBSCRIBE,new UnsubscribeHandler());
121         _handlers.put(META_PING,new PingHandler());
122         
123         setTimeout(getTimeout());
124     }
125 
126     /* ------------------------------------------------------------ */
127     public void addExtension(Extension ext)
128     {
129         _extensions.add(ext);
130     }
131     
132     /* ------------------------------------------------------------ */
133     public List<Extension> getExtensions()
134     {
135         // TODO - remove this hack of a method!
136         return _extensions;
137     }
138 
139     /* ------------------------------------------------------------ */
140     public void removeExtension(Extension ext)
141     {
142         _extensions.remove(ext);
143     }
144     
145     /* ------------------------------------------------------------ */
146     /**
147      * @param id
148      * @return
149      */
150     public ChannelImpl getChannel(ChannelId id)
151     {
152         return _root.getChild(id);
153     }
154 
155     /* ------------------------------------------------------------ */
156     public ChannelImpl getChannel(String id)
157     {
158         ChannelId cid=getChannelId(id);
159 
160         if (cid.depth()==0)
161             return null;
162         return _root.getChild(cid);
163     }
164 
165     /* ------------------------------------------------------------ */
166     public Channel getChannel(String id, boolean create)
167     {
168         synchronized(this)
169         {
170             ChannelImpl channel=getChannel(id);
171 
172             if (channel==null && create)
173             {
174                 channel=new ChannelImpl(id,this);
175                 _root.addChild(channel);
176                 
177                 if (isLogInfo())
178                     logInfo("newChannel: "+channel);
179             }
180             return channel;
181         }
182     }
183     
184     /* ------------------------------------------------------------ */
185     public ChannelId getChannelId(String id)
186     {
187         ChannelId cid = _channelIdCache.get(id);
188         if (cid==null)
189         {
190             // TODO shrink cache!
191             cid=new ChannelId(id);
192             _channelIdCache.put(id,cid);
193         }
194         return cid;
195     }
196 
197     /* ------------------------------------------------------------ */
198     /* (non-Javadoc)
199      * @see org.mortbay.cometd.Bx#getClient(java.lang.String)
200      */
201     public Client getClient(String client_id)
202     {
203         synchronized(this)
204         {
205             if (client_id==null)
206                 return null;
207             Client client = _clients.get(client_id);
208             return client;
209         }
210     }
211 
212     /* ------------------------------------------------------------ */
213     public Set<String> getClientIDs()
214     {
215         return _clients.keySet();
216     }
217 
218     /* ------------------------------------------------------------ */
219     /**
220      * @return The maximum time in ms to wait between polls before timing out a client
221      */
222     public long getMaxInterval()
223     {
224         return _maxInterval;
225     }
226 
227     /* ------------------------------------------------------------ */
228     /**
229      * @return the logLevel. 0=none, 1=info, 2=debug
230      */
231     public int getLogLevel()
232     {
233         return _logLevel;
234     }
235 
236     /* ------------------------------------------------------------ */
237     /* (non-Javadoc)
238      * @see org.mortbay.cometd.Bx#getSecurityPolicy()
239      */
240     public SecurityPolicy getSecurityPolicy()
241     {
242         return _securityPolicy;
243     }
244 
245     /* ------------------------------------------------------------ */
246     public long getTimeout()
247     {
248         return _timeout;
249     }
250 
251     /* ------------------------------------------------------------ */
252     public long getInterval()
253     {
254         return _interval;
255     }
256 
257     /* ------------------------------------------------------------ */
258     /**
259      * @return true if published messages are directly delivered to subscribers. False if
260      * a new message is to be created that holds only supported fields.
261      */
262     public boolean isDirectDeliver()
263     {
264         return _directDeliver;
265     }
266 
267     /* ------------------------------------------------------------ */
268     /**
269      * @param directDeliver true if published messages are directly delivered to subscribers. False if
270      * a new message is to be created that holds only supported fields.
271      */
272     public void setDirectDeliver(boolean directDeliver)
273     {
274         _directDeliver = directDeliver;
275     }
276     
277     /* ------------------------------------------------------------ */
278     /** Handle a Bayeux message.
279      * This is normally only called by the bayeux servlet or a test harness.
280      * @param client The client if known
281      * @param transport The transport to use for the message
282      * @param message The bayeux message.
283      */
284     public String handle(ClientImpl client, Transport transport, Message message) throws IOException
285     {
286         String channel_id=message.getChannel();
287         
288         Handler handler=(Handler)_handlers.get(channel_id);
289         if (handler!=null)
290         {
291             // known meta channel
292             ListIterator<Extension> iter = _extensions.listIterator(_extensions.size());
293             while(iter.hasPrevious())
294                 message=iter.previous().rcvMeta(message);
295             
296             handler.handle(client,transport,message);
297             _metaPublishHandler.handle(client,transport,message);
298         }
299         else if (channel_id.startsWith(META_SLASH))
300         {
301             // unknown meta channel
302             ListIterator<Extension> iter = _extensions.listIterator(_extensions.size());
303             while(iter.hasPrevious())
304                 message=iter.previous().rcvMeta(message);
305             _metaPublishHandler.handle(client,transport,message);
306         }
307         else
308         {
309             // non meta channel
310             handler=_publishHandler;
311             ListIterator<Extension> iter = _extensions.listIterator(_extensions.size());
312             while(iter.hasPrevious())
313                 message=iter.previous().rcv(message);
314             handler.handle(client,transport,message);
315         }
316 
317         return channel_id;
318     }
319 
320     /* ------------------------------------------------------------ */
321     public boolean hasChannel(String id)
322     {
323         ChannelId cid=getChannelId(id);
324         return _root.getChild(cid)!=null;
325     }
326 
327     /* ------------------------------------------------------------ */
328     public boolean isInitialized()
329     {
330         return _initialized;
331     }
332 
333     /* ------------------------------------------------------------ */
334     /**
335      * @return the commented
336      */
337     public boolean isJSONCommented()
338     {
339         return _JSONCommented;
340     }
341 
342     /* ------------------------------------------------------------ */
343     public boolean isLogDebug()
344     {
345         return _logLevel>1;
346     }
347 
348     /* ------------------------------------------------------------ */
349     public boolean isLogInfo()
350     {
351         return _logLevel>0;
352     }
353     
354     /* ------------------------------------------------------------ */
355     public void logDebug(String message)
356     {
357         if (_logLevel>1)
358             _context.log(message);
359     }
360 
361     /* ------------------------------------------------------------ */
362     public void logDebug(String message, Throwable th)
363     {
364         if (_logLevel>1)
365             _context.log(message,th);
366     }
367 
368     /* ------------------------------------------------------------ */
369     public void logWarn(String message, Throwable th)
370     {
371         _context.log(message+": "+th.toString());
372     }
373 
374     /* ------------------------------------------------------------ */
375     public void logWarn(String message)
376     {
377         _context.log(message);
378     }
379 
380     /* ------------------------------------------------------------ */
381     public void logInfo(String message)
382     {
383         if (_logLevel>0)
384             _context.log(message);
385     }
386 
387     /* ------------------------------------------------------------ */
388     public Client newClient(String idPrefix)
389     {
390         ClientImpl client = new ClientImpl(this,idPrefix);
391         return client;
392     }
393     
394     /* ------------------------------------------------------------ */
395     public abstract ClientImpl newRemoteClient();
396 
397     /* ------------------------------------------------------------ */
398     /** Create new transport object for a bayeux message
399      * @param client The client
400      * @param message the bayeux message
401      * @return the negotiated transport.
402      */
403     public Transport newTransport(ClientImpl client, Map<?,?> message)
404     {
405         if (isLogDebug())
406             logDebug("newTransport: client="+client+",message="+message);
407 
408         Transport result=null;
409 
410         try
411         {
412             String type=client==null?null:client.getConnectionType();
413             if (type==null)
414                 type=(String)message.get(Bayeux.CONNECTION_TYPE_FIELD);
415 
416             if (Bayeux.TRANSPORT_CALLBACK_POLL.equals(type) || type==null) 
417             {
418                 String jsonp=(String)message.get(Bayeux.JSONP_PARAMETER);
419                 if(jsonp!=null)
420                     result=new JSONPTransport(client!=null&&client.isJSONCommented(),jsonp);
421                 else
422                     result=new JSONTransport(client!=null&&client.isJSONCommented());
423             }
424             else
425                 result=new JSONTransport(client!=null&&client.isJSONCommented());
426                 
427         }
428         catch (Exception e)
429         {
430             throw new RuntimeException(e);
431         }
432 
433         if (isLogDebug())
434             logDebug("newTransport: result="+result);
435         return result;
436     }
437 
438     /* ------------------------------------------------------------ */
439     /** Publish data to a channel.
440      * Creates a message and delivers it to the root channel.
441      * @param to
442      * @param from
443      * @param data
444      * @param msgId
445      */
446     protected void doPublish(ChannelId to, Client from, Object data, String msgId)
447     {
448         Message msg = newMessage();
449         msg.put(CHANNEL_FIELD,to.toString());
450         
451         if (msgId==null)
452         {
453             long id=msg.hashCode()
454             ^(to==null?0:to.hashCode())
455             ^(from==null?0:from.hashCode());
456             id=id<0?-id:id;
457             msg.put(ID_FIELD,Long.toString(id,36));
458         }
459         else
460             msg.put(ID_FIELD,msgId);
461             
462         msg.put(DATA_FIELD,data);
463         
464         for (Extension e:_extensions)
465             msg=e.send(msg);
466         _root.doDelivery(to,from,msg);
467         ((MessageImpl)msg).decRef();
468     }
469 
470     /* ------------------------------------------------------------ */
471     public boolean removeChannel(ChannelImpl channel)
472     {
473         boolean removed = _root.doRemove(channel);
474         if (removed)
475             for (ChannelBayeuxListener l : _channelListeners)
476                 l.channelRemoved(channel);
477         return removed;
478     }
479 
480     /* ------------------------------------------------------------ */
481     public void addChannel(ChannelImpl channel)
482     {
483         for (ChannelBayeuxListener l : _channelListeners)
484             l.channelAdded(channel);
485     }
486     
487     /* ------------------------------------------------------------ */
488     protected String newClientId(long variation, String idPrefix)
489     {
490         if (idPrefix==null)
491             return Long.toString(getRandom(),36)+Long.toString(variation,36);
492         else
493             return idPrefix+"_"+Long.toString(getRandom(),36);
494     }
495 
496     /* ------------------------------------------------------------ */
497     protected void addClient(ClientImpl client,String idPrefix)
498     {
499         while(true)
500         {
501             String id = newClientId(client.hashCode(),idPrefix);
502             client.setId(id);
503             
504             ClientImpl other = _clients.putIfAbsent(id,client);
505             if (other==null)
506             {
507                 for (ClientBayeuxListener l : _clientListeners)
508                     l.clientAdded((Client)client);
509                     
510                 return;
511             }
512         }
513     }
514     
515     /* ------------------------------------------------------------ */
516     /* (non-Javadoc)
517      * @see org.mortbay.cometd.Bx#removeClient(java.lang.String)
518      */
519     public Client removeClient(String client_id)
520     {
521         ClientImpl client;
522         synchronized(this)
523         {
524             if (client_id==null)
525                 return null;
526             client = _clients.remove(client_id);
527         }
528         if (client!=null)
529         {
530             client.unsubscribeAll();
531             for (ClientBayeuxListener l : _clientListeners)
532                 l.clientRemoved((Client)client);
533         }
534         return client;
535     }
536 
537     /* ------------------------------------------------------------ */
538     /**
539      * @param ms The maximum time in ms to wait between polls before timing out a client
540      */
541     public void setMaxInterval(long ms)
542     {
543         _maxInterval=ms;
544     }
545 
546     /* ------------------------------------------------------------ */
547     /**
548      * @param commented the commented to set
549      */
550     public void setJSONCommented(boolean commented)
551     {
552         _JSONCommented=commented;
553     }
554 
555     /* ------------------------------------------------------------ */
556     /**
557      * @param logLevel
558      *            the logLevel: 0=none, 1=info, 2=debug
559      */
560     public void setLogLevel(int logLevel)
561     {
562         _logLevel=logLevel;
563     }
564     
565     /* ------------------------------------------------------------ */
566     /* (non-Javadoc)
567      * @see org.mortbay.cometd.Bx#setSecurityPolicy(org.mortbay.cometd.SecurityPolicy)
568      */
569     public void setSecurityPolicy(SecurityPolicy securityPolicy)
570     {
571         _securityPolicy=securityPolicy;
572     }
573 
574     
575     /* ------------------------------------------------------------ */
576     public void setTimeout(long ms)
577     {
578         _timeout = ms;
579         generateAdvice();
580     }
581 
582     
583     /* ------------------------------------------------------------ */
584     public void setInterval(long ms)
585     {
586         _interval = ms;
587         generateAdvice();
588     }
589 
590     /* ------------------------------------------------------------ */
591     /**
592      * The time a client should delay between reconnects when multiple
593      * connections from the same browser are detected. This effectively 
594      * produces traditional polling.
595      * @param multiFrameInterval the multiFrameInterval to set
596      */
597     public void setMultiFrameInterval(int multiFrameInterval)
598     {
599         _multiFrameInterval=multiFrameInterval;
600         generateAdvice();
601     }
602     
603     /* ------------------------------------------------------------ */
604     /**
605      * @return the multiFrameInterval in milliseconds
606      */
607     public int getMultiFrameInterval()
608     {
609         return _multiFrameInterval;
610     }
611 
612     /* ------------------------------------------------------------ */
613     void generateAdvice()
614     {
615         setAdvice(new JSON.Literal("{\"reconnect\":\"retry\",\"interval\":"+getInterval()+",\"timeout\":"+getTimeout()+"}"));     
616     }
617 
618     /* ------------------------------------------------------------ */
619     public void setAdvice(JSON.Literal advice)
620     {
621         synchronized(this)
622         {
623             _adviceVersion++;
624             _advice=advice;
625             _multiFrameAdvice=new JSON.Literal(JSON.toString(multiFrameAdvice(advice)));
626         }
627     }
628 
629     /* ------------------------------------------------------------ */
630     private Map<String,Object> multiFrameAdvice(JSON.Literal advice)
631     {
632         Map<String,Object> a = (Map<String,Object>)JSON.parse(_advice.toString());
633         a.put("multiple-clients",Boolean.TRUE);
634         if (_multiFrameInterval>0)
635         {
636             a.put("reconnect","retry");
637             a.put("interval",_multiFrameInterval);
638         }
639         else
640             a.put("reconnect","none");
641         return a;
642     }
643     
644     
645     
646     /* ------------------------------------------------------------ */
647     public JSON.Literal getAdvice()
648     {
649         return _advice;
650     }
651     
652     /* ------------------------------------------------------------ */
653     /**
654      * @return TRUE if {@link #getCurrentRequest()} will return the current request
655      */
656     public boolean isRequestAvailable()
657     {
658         return _requestAvailable;
659     }
660 
661     /* ------------------------------------------------------------ */
662     /**
663      * @param requestAvailable TRUE if {@link #getCurrentRequest()} will return the current request
664      */
665     public void setRequestAvailable(boolean requestAvailable)
666     {
667         _requestAvailable=requestAvailable;
668     }
669 
670     /* ------------------------------------------------------------ */
671     /**
672      * @return the current request if {@link #isRequestAvailable()} is true, else null
673      */
674     public HttpServletRequest getCurrentRequest()
675     {
676         return _request.get();
677     }
678     
679     /* ------------------------------------------------------------ */
680     /**
681      * @return the current request if {@link #isRequestAvailable()} is true, else null
682      */
683     void setCurrentRequest(HttpServletRequest request)
684     {
685         _request.set(request);
686     }
687 
688 
689     
690     /* ------------------------------------------------------------ */
691     public Collection<Channel> getChannels()
692     {
693         List<Channel> channels = new ArrayList<Channel>();
694         _root.getChannels(channels);
695         return channels;
696     }
697     
698     /* ------------------------------------------------------------ */
699     /**
700      * @return
701      */
702     public int getChannelCount()
703     {
704         return _root.getChannelCount();
705     }
706     
707     /* ------------------------------------------------------------ */
708     public Collection<Client> getClients()
709     {
710         synchronized(this)
711         {
712             return new ArrayList<Client>(_clients.values());
713         }
714     }
715 
716     /* ------------------------------------------------------------ */
717     /**
718      * @return
719      */
720     public int getClientCount()
721     {
722         synchronized(this)
723         {
724             return _clients.size();
725         }
726     }
727     
728     /* ------------------------------------------------------------ */
729     public boolean hasClient(String clientId)
730     {
731         synchronized(this)      
732         {
733             if (clientId==null)
734                 return false;
735             return _clients.containsKey(clientId);
736         }
737     }
738 
739     /* ------------------------------------------------------------ */
740     public Channel removeChannel(String channelId)
741     {
742         Channel channel = getChannel(channelId);
743         boolean removed = false;
744         if (channel!=null)
745             removed = channel.remove();
746         
747         if (removed) 
748             return channel;
749         else
750             return null;
751     }
752 
753     /* ------------------------------------------------------------ */
754     protected void initialize(ServletContext context)
755     {
756         synchronized(this)
757         {
758             _initialized=true;
759             _context=context;
760             try
761             {
762                 _random=SecureRandom.getInstance("SHA1PRNG");
763             }
764             catch (Exception e)
765             {
766                 context.log("Could not get secure random for ID generation",e);
767                 _random=new Random();
768             }
769             _random.setSeed(_random.nextLong()^hashCode()^(context.hashCode()<<32)^Runtime.getRuntime().freeMemory());
770             _channelIdCache=new ConcurrentHashMap<String, ChannelId>();
771             
772             _root.addChild(new ServiceChannel(Bayeux.SERVICE));
773             
774         }
775     }
776 
777     /* ------------------------------------------------------------ */
778     long getRandom()
779     {
780         long l=_random.nextLong();
781         return l<0?-l:l;
782     }
783 
784     /* ------------------------------------------------------------ */
785     void clientOnBrowser(String browserId,String clientId)
786     {
787         List<String> clients=_browser2client.get(browserId);
788         if (clients==null)
789         {
790             List<String> new_clients=new CopyOnWriteArrayList<String>();
791             clients=_browser2client.putIfAbsent(browserId,new_clients);
792             if (clients==null)
793                 clients=new_clients;
794         }
795         clients.add(clientId);
796     }
797 
798     /* ------------------------------------------------------------ */
799     void clientOffBrowser(String browserId,String clientId)
800     {
801         List<String> clients=_browser2client.get(browserId);
802         if (clients!=null)
803             clients.remove(clientId);
804     }
805     
806     /* ------------------------------------------------------------ */
807     List<String> clientsOnBrowser(String browserId)
808     {
809         List<String> clients=_browser2client.get(browserId);
810         if (clients==null)
811             return Collections.emptyList();
812         return clients;
813     }
814     
815     /* ------------------------------------------------------------ */
816     public void addListener(BayeuxListener listener)
817     {
818         if (listener instanceof ClientBayeuxListener)
819             _clientListeners.add((ClientBayeuxListener)listener);
820         else if (listener instanceof ChannelBayeuxListener)
821             _channelListeners.add((ChannelBayeuxListener)listener);
822     }    
823       
824     
825     /* ------------------------------------------------------------ */
826     public int getMaxClientQueue()
827     {
828         return _maxClientQueue;
829     }
830 
831     /* ------------------------------------------------------------ */
832     public void setMaxClientQueue(int size)
833     {
834         _maxClientQueue=size;
835     }
836 
837 
838 
839     /* ------------------------------------------------------------ */
840     /* ------------------------------------------------------------ */
841     public static class DefaultPolicy implements SecurityPolicy
842     {
843         public boolean canHandshake(Message message)
844         {
845             return true;
846         }
847         
848         public boolean canCreate(Client client, String channel, Message message)
849         {
850             return client!=null && !channel.startsWith(Bayeux.META_SLASH);
851         }
852 
853         public boolean canSubscribe(Client client, String channel, Message message)
854         {
855 	    if (client!=null && ("/**".equals(channel) || "/*".equals(channel)))
856 	        return false;
857             return client!=null && !channel.startsWith(Bayeux.META_SLASH);
858         }
859 
860         public boolean canPublish(Client client, String channel, Message message)
861         {
862             return client!=null || client==null && Bayeux.META_HANDSHAKE.equals(channel);
863         }
864 
865     }
866 
867 
868     /* ------------------------------------------------------------ */
869     /* ------------------------------------------------------------ */
870     protected abstract class Handler
871     {
872         abstract void handle(ClientImpl client, Transport transport, Message message) throws IOException;
873         abstract ChannelId getMetaChannelId();
874         void unknownClient(Transport transport,String channel) throws IOException
875         {
876             MessageImpl reply=newMessage();
877             
878             reply.put(CHANNEL_FIELD,channel);
879             reply.put(SUCCESSFUL_FIELD,Boolean.FALSE);
880             reply.put(ERROR_FIELD,"402::Unknown client");
881             reply.put("advice",_handshakeAdvice);
882             transport.send(reply);
883         }
884     }
885 
886     /* ------------------------------------------------------------ */
887     /* ------------------------------------------------------------ */
888     protected class ConnectHandler extends Handler
889     {
890         protected String _metaChannel=META_CONNECT;
891 
892         @Override
893         ChannelId getMetaChannelId()
894         {
895             return META_CONNECT_ID;
896         }
897 
898         @Override
899         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
900         {      
901             if (client==null)
902             {
903                 unknownClient(transport,_metaChannel);
904                 return;
905             }
906 
907             // is this the first connect message?
908             String type=client.getConnectionType();
909             boolean polling=true;
910             if (type==null)
911             {
912                 type=(String)message.get(Bayeux.CONNECTION_TYPE_FIELD);
913                 client.setConnectionType(type);
914                 polling=false;
915             }
916 
917             Object advice = message.get(ADVICE_FIELD);
918             if (advice!=null)
919             {
920                 Long timeout=(Long)((Map)advice).get("timeout");
921                 if (timeout!=null && timeout.longValue()>0)
922                     client.setTimeout(timeout.longValue());
923                 else
924                     client.setTimeout(0);
925             }
926             else
927                 client.setTimeout(0);
928             
929             advice=null; 
930         
931             // Work out if multiple clients from some browser?
932             if (polling && _multiFrameInterval>0 && client.getBrowserId()!=null)
933             {
934                 List<String> clients=clientsOnBrowser(client.getBrowserId());
935                 int count=clients.size();
936                 if (count>1)
937                 {
938                     polling=clients.get(0).equals(client.getId());
939                     advice=client.getAdvice();
940                     if (advice==null)
941                         advice=_multiFrameAdvice;
942                     else // could probably cache this 
943                         advice=multiFrameAdvice((JSON.Literal)advice);
944                 }
945             }
946 
947             synchronized(this)
948             {
949                 if (advice==null)
950                 {
951                     if (_adviceVersion!=client._adviseVersion)
952                     {
953                         advice=_advice;
954                         client._adviseVersion=_adviceVersion;
955                     }
956                 }
957                 else
958                     client._adviseVersion=-1; // clear so it is reset after multi state clears
959             }
960            
961             // reply to connect message
962             String id=message.getId(); 
963 
964             Message reply=newMessage(message);
965             
966             reply.put(CHANNEL_FIELD,META_CONNECT);
967             reply.put(SUCCESSFUL_FIELD,Boolean.TRUE);
968             if (advice!=null)
969                 reply.put(ADVICE_FIELD,advice);
970             if (id!=null)
971                 reply.put(ID_FIELD,id);
972 
973             if (polling)
974                 transport.setPollReply(reply);
975             else
976             {
977                 for (Extension e:_extensions)
978                     reply=e.sendMeta(reply);
979                 transport.send(reply);
980             }
981         }
982     }
983     
984     /* ------------------------------------------------------------ */
985     /* ------------------------------------------------------------ */
986     protected class DisconnectHandler extends Handler
987     {
988         @Override
989         ChannelId getMetaChannelId()
990         {
991             return META_DISCONNECT_ID;
992         }
993 
994         @Override
995         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
996         {
997             if (client==null)
998             {
999                 unknownClient(transport,META_DISCONNECT);
1000                 return;
1001             }
1002             if (isLogInfo())
1003                 logInfo("Disconnect "+client.getId());
1004 
1005             client.remove(false);
1006             
1007             Message reply=newMessage(message);
1008             reply.put(CHANNEL_FIELD,META_DISCONNECT);
1009             reply.put(SUCCESSFUL_FIELD,Boolean.TRUE);
1010             String id=message.getId(); 
1011             if (id!=null)
1012                 reply.put(ID_FIELD,id);
1013 
1014             for (Extension e:_extensions)
1015                 reply=e.sendMeta(reply);
1016             
1017             Message pollReply = transport.getPollReply();
1018             if (pollReply!=null)
1019             {
1020                 for (Extension e:_extensions)
1021                     pollReply=e.sendMeta(pollReply);
1022                 transport.send(pollReply);
1023                 transport.setPollReply(null);
1024             }
1025             transport.send(reply);
1026         }
1027     }
1028 
1029 
1030     /* ------------------------------------------------------------ */
1031     /* ------------------------------------------------------------ */
1032     protected class HandshakeHandler extends Handler
1033     {
1034         @Override
1035         ChannelId getMetaChannelId()
1036         {
1037             return META_HANDSHAKE_ID;
1038         }
1039 
1040         @Override
1041         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
1042         {
1043             if (client!=null)
1044                 throw new IllegalStateException();
1045 
1046             if (_securityPolicy!=null && !_securityPolicy.canHandshake(message))
1047             {
1048                 Message reply=newMessage(message);
1049                 reply.put(CHANNEL_FIELD,META_HANDSHAKE);
1050                 reply.put(SUCCESSFUL_FIELD,Boolean.FALSE);
1051                 reply.put(ERROR_FIELD,"403::Handshake denied");
1052 
1053                 for (Extension e:_extensions)
1054                     reply=e.sendMeta(reply);
1055                 
1056                 transport.send(reply);
1057                 return;
1058             }
1059             
1060             client=newRemoteClient();
1061 
1062             Map<?,?> ext = (Map<?,?>)message.get(EXT_FIELD);
1063 
1064             boolean commented=_JSONCommented && ext!=null && Boolean.TRUE.equals(ext.get("json-comment-filtered"));
1065             
1066             Message reply=newMessage(message);
1067             reply.put(CHANNEL_FIELD,META_HANDSHAKE);
1068             reply.put("version","1.0");
1069             reply.put("minimumVersion","0.9");
1070             if (isJSONCommented())
1071                 reply.put(EXT_FIELD,EXT_JSON_COMMENTED);
1072 
1073             if (client!=null)
1074             {
1075                 reply.put("supportedConnectionTypes",_transports);
1076                 reply.put("successful",Boolean.TRUE);
1077                 reply.put(CLIENT_FIELD,client.getId());
1078                 if (_advice!=null)
1079                     reply.put(ADVICE_FIELD,_advice);
1080                 client.setJSONCommented(commented);
1081                 transport.setJSONCommented(commented);
1082             }
1083             else
1084             {
1085                 reply.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
1086                 if (_advice!=null)
1087                     reply.put(ADVICE_FIELD,_advice);
1088             }
1089 
1090             if (isLogDebug())
1091                 logDebug("handshake.handle: reply="+reply);
1092 
1093             String id=message.getId();
1094             if (id!=null)
1095                 reply.put(ID_FIELD,id);
1096 
1097             for (Extension e:_extensions)
1098                 reply=e.sendMeta(reply);
1099             transport.send(reply);
1100         }
1101     }
1102 
1103     /* ------------------------------------------------------------ */
1104     /* ------------------------------------------------------------ */
1105     protected class PublishHandler extends Handler
1106     {
1107         @Override
1108         ChannelId getMetaChannelId()
1109         {
1110             return null;
1111         }
1112 
1113         @Override
1114         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
1115         {
1116             String channel_id=message.getChannel();
1117             
1118             if (client==null && message.containsKey(CLIENT_FIELD))
1119             {
1120                 unknownClient(transport,channel_id);
1121                 return;
1122             }
1123             
1124             String id=message.getId();
1125 
1126             ChannelId cid=getChannelId(channel_id);
1127             Object data=message.get(Bayeux.DATA_FIELD);
1128 
1129             Message reply=newMessage(message);
1130             reply.put(CHANNEL_FIELD,channel_id);
1131             if (id!=null)
1132                 reply.put(ID_FIELD,id);
1133                 
1134             if (data!=null&&_securityPolicy.canPublish(client,channel_id,message))
1135             {
1136                 reply.put(SUCCESSFUL_FIELD,Boolean.TRUE);
1137 
1138                 for (Extension e:_extensions)
1139                     reply=e.sendMeta(reply);
1140                 
1141                 transport.send(reply);
1142                 if (_directDeliver)
1143                 {
1144                     message.remove(CLIENT_FIELD);
1145                     for (Extension e:_extensions)
1146                         message=e.send(message);
1147                     _root.doDelivery(cid,client,message);
1148                 }
1149                 else
1150                     doPublish(cid,client,data,id==null?null:id);
1151             }
1152             else
1153             {
1154                 reply.put(SUCCESSFUL_FIELD,Boolean.FALSE);
1155                 reply.put(ERROR_FIELD,"403::Publish denied");
1156 
1157                 for (Extension e:_extensions)
1158                     reply=e.sendMeta(reply);
1159                 transport.send(reply);
1160             }
1161         }
1162     }
1163 
1164     /* ------------------------------------------------------------ */
1165     /* ------------------------------------------------------------ */
1166     protected class MetaPublishHandler extends Handler
1167     {
1168         @Override
1169         ChannelId getMetaChannelId()
1170         {
1171             return null;
1172         }
1173 
1174         @Override
1175         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
1176         {
1177             String channel_id=message.getChannel();
1178             
1179             if (client==null && !META_HANDSHAKE.equals(channel_id))
1180             {
1181                 // unknown client
1182                 return;
1183             }
1184             
1185             if(_securityPolicy.canPublish(client,channel_id,message))
1186             {
1187                 _root.doDelivery(getChannelId(channel_id),client,message);
1188             }
1189         }
1190     }
1191 
1192     /* ------------------------------------------------------------ */
1193     /* ------------------------------------------------------------ */
1194     protected class SubscribeHandler extends Handler
1195     {
1196         @Override
1197         ChannelId getMetaChannelId()
1198         {
1199             return META_SUBSCRIBE_ID;
1200         }
1201 
1202         @Override
1203         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
1204         {
1205             if (client==null)
1206             {
1207                 unknownClient(transport,META_SUBSCRIBE);
1208                 return;
1209             }
1210 
1211             String subscribe_id=(String)message.get(SUBSCRIPTION_FIELD);
1212 
1213             // select a random channel ID if none specifified
1214             if (subscribe_id==null)
1215             {
1216                 subscribe_id=Long.toString(getRandom(),36);
1217                 while (getChannel(subscribe_id)!=null)
1218                     subscribe_id=Long.toString(getRandom(),36);
1219             }
1220 
1221             ChannelId cid=null;
1222             boolean can_subscribe=false;
1223             
1224             if (subscribe_id.startsWith(Bayeux.SERVICE_SLASH))
1225             {
1226                 can_subscribe=true;
1227             }
1228             else if (subscribe_id.startsWith(Bayeux.META_SLASH))
1229             {
1230                 can_subscribe=false;
1231             }
1232             else
1233             {
1234                 cid=getChannelId(subscribe_id);
1235                 can_subscribe=_securityPolicy.canSubscribe(client,subscribe_id,message);
1236             }
1237                 
1238             Message reply=newMessage(message);
1239             reply.put(CHANNEL_FIELD,META_SUBSCRIBE);
1240             reply.put(SUBSCRIPTION_FIELD,subscribe_id);
1241 
1242             if (can_subscribe)
1243             {
1244                 if (cid!=null)
1245                 {
1246                     ChannelImpl channel=getChannel(cid);
1247                     if (channel==null&&_securityPolicy.canCreate(client,subscribe_id,message))
1248                         channel=(ChannelImpl)getChannel(subscribe_id, true);
1249 
1250                     if (channel!=null)
1251                         channel.subscribe(client);
1252                     else
1253                         can_subscribe=false;
1254                 }
1255                         
1256                 if (can_subscribe)
1257                 {
1258                     reply.put(SUCCESSFUL_FIELD,Boolean.TRUE);
1259                 }
1260                 else 
1261                 {
1262                     reply.put(SUCCESSFUL_FIELD,Boolean.FALSE);
1263                     reply.put(ERROR_FIELD,"403::cannot create");
1264                 }
1265             }
1266             else
1267             {
1268                 reply.put(SUCCESSFUL_FIELD,Boolean.FALSE);
1269                 reply.put(ERROR_FIELD,"403::cannot subscribe");
1270                 
1271             }
1272 
1273             String id=message.getId(); 
1274             if (id!=null)
1275                 reply.put(ID_FIELD,id);
1276             for (Extension e:_extensions)
1277                 reply=e.sendMeta(reply);
1278             transport.send(reply);
1279         }
1280     }
1281 
1282     /* ------------------------------------------------------------ */
1283     /* ------------------------------------------------------------ */
1284     protected class UnsubscribeHandler extends Handler
1285     {
1286         @Override
1287         ChannelId getMetaChannelId()
1288         {
1289             return META_UNSUBSCRIBE_ID;
1290         }
1291 
1292         @Override
1293         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
1294         {
1295             if (client==null)
1296             {
1297                 unknownClient(transport,META_UNSUBSCRIBE);
1298                 return;
1299             }
1300 
1301             String channel_id=(String)message.get(SUBSCRIPTION_FIELD);
1302             ChannelImpl channel=getChannel(channel_id);
1303             if (channel!=null)
1304                 channel.unsubscribe(client);
1305 
1306             Message reply=newMessage(message);
1307             reply.put(CHANNEL_FIELD,META_UNSUBSCRIBE);
1308             if (channel!=null)
1309             {
1310                 channel.unsubscribe(client);
1311                 reply.put(SUBSCRIPTION_FIELD,channel.getId());
1312                 reply.put(SUCCESSFUL_FIELD,Boolean.TRUE);
1313             }
1314             else
1315                 reply.put(SUCCESSFUL_FIELD,Boolean.FALSE);
1316                 
1317             String id=message.getId(); 
1318             if (id!=null)
1319                 reply.put(ID_FIELD,id);
1320             for (Extension e:_extensions)
1321                 reply=e.sendMeta(reply);
1322             transport.send(reply);
1323         }
1324     }
1325 
1326     /* ------------------------------------------------------------ */
1327     /* ------------------------------------------------------------ */
1328     protected class PingHandler extends Handler
1329     {
1330         @Override
1331         ChannelId getMetaChannelId()
1332         {
1333             return META_PING_ID;
1334         }
1335 
1336         @Override
1337         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
1338         {
1339             Message reply=newMessage(message);
1340             reply.put(CHANNEL_FIELD,META_PING);
1341             reply.put(SUCCESSFUL_FIELD,Boolean.TRUE);
1342                 
1343             String id=message.getId(); 
1344             if (id!=null)
1345                 reply.put(ID_FIELD,id);
1346             for (Extension e:_extensions)
1347                 reply=e.sendMeta(reply);
1348             transport.send(reply);
1349         }
1350     }
1351 
1352     
1353     /* ------------------------------------------------------------ */
1354     /* ------------------------------------------------------------ */
1355     protected class ServiceChannel extends ChannelImpl
1356     {
1357         ServiceChannel(String id)
1358         {
1359             super(id,AbstractBayeux.this);
1360         }
1361 
1362         /* ------------------------------------------------------------ */
1363         /* (non-Javadoc)
1364          * @see org.mortbay.cometd.ChannelImpl#addChild(org.mortbay.cometd.ChannelImpl)
1365          */
1366         @Override
1367         public void addChild(ChannelImpl channel)
1368         {
1369             super.addChild(channel);
1370             setPersistent(true);
1371         }
1372 
1373         /* ------------------------------------------------------------ */
1374         @Override
1375         public void subscribe(Client client)
1376         {
1377             if (client.isLocal())
1378                 super.subscribe(client);
1379         }
1380     }
1381 }