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