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, boolean lazy)
418     {
419         final MessageImpl 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.setLazy(lazy);
435 
436         final Message m=extendSendBayeux(from,message);
437 
438         if (m!=null)
439             _root.doDelivery(to,from,m);
440         if (m instanceof MessageImpl)
441             ((MessageImpl)m).decRef();
442     }
443 
444     /* ------------------------------------------------------------ */
445     public boolean removeChannel(ChannelImpl channel)
446     {
447         return _root.doRemove(channel,_channelListeners);
448     }
449 
450     /* ------------------------------------------------------------ */
451     public void addChannel(ChannelImpl channel)
452     {
453         for (ChannelBayeuxListener l : _channelListeners)
454             l.channelAdded(channel);
455     }
456 
457     /* ------------------------------------------------------------ */
458     protected String newClientId(long variation, String idPrefix)
459     {
460         if (idPrefix==null)
461             return Long.toString(getRandom(),36)+Long.toString(variation,36);
462         else
463             return idPrefix+"_"+Long.toString(getRandom(),36);
464     }
465 
466     /* ------------------------------------------------------------ */
467     protected void addClient(ClientImpl client,String idPrefix)
468     {
469         while(true)
470         {
471             String id = newClientId(client.hashCode(),idPrefix);
472             client.setId(id);
473 
474             ClientImpl other = _clients.putIfAbsent(id,client);
475             if (other==null)
476             {
477                 for (ClientBayeuxListener l : _clientListeners)
478                     l.clientAdded((Client)client);
479 
480                 return;
481             }
482         }
483     }
484 
485     /* ------------------------------------------------------------ */
486     /* (non-Javadoc)
487      * @see org.mortbay.cometd.Bx#removeClient(java.lang.String)
488      */
489     public Client removeClient(String client_id)
490     {
491         ClientImpl client;
492         synchronized(this)
493         {
494             if (client_id==null)
495                 return null;
496             client = _clients.remove(client_id);
497         }
498         if (client!=null)
499         {
500             for (ClientBayeuxListener l : _clientListeners)
501                 l.clientRemoved((Client)client);
502             client.unsubscribeAll();
503         }
504         return client;
505     }
506 
507     /* ------------------------------------------------------------ */
508     /**
509      * @param ms The maximum time in ms to wait between polls before timing out a client
510      */
511     public void setMaxInterval(long ms)
512     {
513         _maxInterval=ms;
514     }
515 
516     /* ------------------------------------------------------------ */
517     /**
518      * @param commented the commented to set
519      */
520     public void setJSONCommented(boolean commented)
521     {
522         if (commented)
523             _context.log("JSONCommented is deprecated");
524     }
525 
526     /* ------------------------------------------------------------ */
527     /**
528      * @param logLevel
529      *            the logLevel: 0=none, 1=info, 2=debug
530      */
531     public void setLogLevel(int logLevel)
532     {
533         _logLevel=logLevel;
534     }
535 
536     /* ------------------------------------------------------------ */
537     /* (non-Javadoc)
538      * @see org.mortbay.cometd.Bx#setSecurityPolicy(org.mortbay.cometd.SecurityPolicy)
539      */
540     public void setSecurityPolicy(SecurityPolicy securityPolicy)
541     {
542         _securityPolicy=securityPolicy;
543     }
544 
545 
546     /* ------------------------------------------------------------ */
547     public void setTimeout(long ms)
548     {
549         _timeout = ms;
550         generateAdvice();
551     }
552 
553 
554     /* ------------------------------------------------------------ */
555     public void setInterval(long ms)
556     {
557         _interval = ms;
558         generateAdvice();
559     }
560 
561     /* ------------------------------------------------------------ */
562     /**
563      * The time a client should delay between reconnects when multiple
564      * connections from the same browser are detected. This effectively
565      * produces traditional polling.
566      * @param multiFrameInterval the multiFrameInterval to set
567      */
568     public void setMultiFrameInterval(int multiFrameInterval)
569     {
570         _multiFrameInterval=multiFrameInterval;
571         generateAdvice();
572     }
573 
574     /* ------------------------------------------------------------ */
575     /**
576      * @return the multiFrameInterval in milliseconds
577      */
578     public int getMultiFrameInterval()
579     {
580         return _multiFrameInterval;
581     }
582 
583     /* ------------------------------------------------------------ */
584     void generateAdvice()
585     {
586         setAdvice(new JSON.Literal("{\"reconnect\":\"retry\",\"interval\":"+getInterval()+",\"timeout\":"+getTimeout()+"}"));
587     }
588 
589     /* ------------------------------------------------------------ */
590     public void setAdvice(JSON.Literal advice)
591     {
592         synchronized(this)
593         {
594             _adviceVersion++;
595             _advice=advice;
596             _multiFrameAdvice=new JSON.Literal(JSON.toString(multiFrameAdvice(advice)));
597         }
598     }
599 
600     /* ------------------------------------------------------------ */
601     private Map<String,Object> multiFrameAdvice(JSON.Literal advice)
602     {
603         Map<String,Object> a = (Map<String,Object>)JSON.parse(_advice.toString());
604         a.put("multiple-clients",Boolean.TRUE);
605         if (_multiFrameInterval>0)
606         {
607             a.put("reconnect","retry");
608             a.put("interval",_multiFrameInterval);
609         }
610         else
611             a.put("reconnect","none");
612         return a;
613     }
614 
615     /* ------------------------------------------------------------ */
616     public JSON.Literal getAdvice()
617     {
618         return _advice;
619     }
620 
621     /* ------------------------------------------------------------ */
622     /**
623      * @return TRUE if {@link #getCurrentRequest()} will return the current request
624      */
625     public boolean isRequestAvailable()
626     {
627         return _requestAvailable;
628     }
629 
630     /* ------------------------------------------------------------ */
631     /**
632      * @param requestAvailable TRUE if {@link #getCurrentRequest()} will return the current request
633      */
634     public void setRequestAvailable(boolean requestAvailable)
635     {
636         _requestAvailable=requestAvailable;
637     }
638 
639     /* ------------------------------------------------------------ */
640     /**
641      * @return the current request if {@link #isRequestAvailable()} is true, else null
642      */
643     public HttpServletRequest getCurrentRequest()
644     {
645         return _request.get();
646     }
647 
648     /* ------------------------------------------------------------ */
649     /**
650      * @return the current request if {@link #isRequestAvailable()} is true, else null
651      */
652     void setCurrentRequest(HttpServletRequest request)
653     {
654         _request.set(request);
655     }
656 
657 
658 
659     /* ------------------------------------------------------------ */
660     public Collection<Channel> getChannels()
661     {
662         List<Channel> channels = new ArrayList<Channel>();
663         _root.getChannels(channels);
664         return channels;
665     }
666 
667     /* ------------------------------------------------------------ */
668     /**
669      * @return
670      */
671     public int getChannelCount()
672     {
673         return _root.getChannelCount();
674     }
675 
676     /* ------------------------------------------------------------ */
677     public Collection<Client> getClients()
678     {
679         synchronized(this)
680         {
681             return new ArrayList<Client>(_clients.values());
682         }
683     }
684 
685     /* ------------------------------------------------------------ */
686     /**
687      * @return
688      */
689     public int getClientCount()
690     {
691         synchronized(this)
692         {
693             return _clients.size();
694         }
695     }
696 
697     /* ------------------------------------------------------------ */
698     public boolean hasClient(String clientId)
699     {
700         synchronized(this)
701         {
702             if (clientId==null)
703                 return false;
704             return _clients.containsKey(clientId);
705         }
706     }
707 
708     /* ------------------------------------------------------------ */
709     public Channel removeChannel(String channelId)
710     {
711         Channel channel = getChannel(channelId);
712 
713         boolean removed = false;
714         if (channel!=null)
715             removed = channel.remove();
716 
717         if (removed)
718             return channel;
719         else
720             return null;
721     }
722 
723     /* ------------------------------------------------------------ */
724     protected void initialize(ServletContext context)
725     {
726         synchronized(this)
727         {
728             _initialized=true;
729             _context=context;
730             try
731             {
732                 _random=SecureRandom.getInstance("SHA1PRNG");
733             }
734             catch (Exception e)
735             {
736                 context.log("Could not get secure random for ID generation",e);
737                 _random=new Random();
738             }
739             _random.setSeed(_random.nextLong()^hashCode()^System.nanoTime()^Runtime.getRuntime().freeMemory());
740             _channelIdCache=new ConcurrentHashMap<String, ChannelId>();
741 
742             _root.addChild(new ServiceChannel(Bayeux.SERVICE));
743 
744         }
745     }
746 
747     /* ------------------------------------------------------------ */
748     long getRandom()
749     {
750         long l=_random.nextLong();
751         return l<0?-l:l;
752     }
753 
754     /* ------------------------------------------------------------ */
755     void clientOnBrowser(String browserId,String clientId)
756     {
757         List<String> clients=_browser2client.get(browserId);
758         if (clients==null)
759         {
760             List<String> new_clients=new CopyOnWriteArrayList<String>();
761             clients=_browser2client.putIfAbsent(browserId,new_clients);
762             if (clients==null)
763                 clients=new_clients;
764         }
765         clients.add(clientId);
766     }
767 
768     /* ------------------------------------------------------------ */
769     void clientOffBrowser(String browserId,String clientId)
770     {
771         List<String> clients=_browser2client.get(browserId);
772         
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         
782         if (clients==null)
783             return Collections.emptyList();
784         return clients;
785     }
786 
787     /* ------------------------------------------------------------ */
788     public void addListener(BayeuxListener listener)
789     {
790         if (listener instanceof ClientBayeuxListener)
791             _clientListeners.add((ClientBayeuxListener)listener);
792         if(listener instanceof ChannelBayeuxListener)
793             _channelListeners.add((ChannelBayeuxListener)listener);
794     }
795 
796     /* ------------------------------------------------------------ */
797     public int getMaxClientQueue()
798     {
799         return _maxClientQueue;
800     }
801 
802     /* ------------------------------------------------------------ */
803     public void setMaxClientQueue(int size)
804     {
805         _maxClientQueue=size;
806     }
807 
808     /* ------------------------------------------------------------ */
809     protected Message extendRcv(ClientImpl from, Message message)
810     {
811         if (_extensions!=null)
812         {
813             for (int i=_extensions.length;message!=null && i-->0;)
814                 message=_extensions[i].rcv(from, message);
815         }
816 
817         if (from!=null)
818         {
819             Extension[] client_exs = from.getExtensions();
820             if (client_exs!=null)
821             {
822                 for (int i=client_exs.length;message!=null && i-->0;)
823                     message=client_exs[i].rcv(from, message);
824             }
825         }
826 
827         return message;
828     }
829 
830     /* ------------------------------------------------------------ */
831     protected Message extendRcvMeta(ClientImpl from, Message message)
832     {
833         if (_extensions!=null)
834         {
835             for (int i=_extensions.length;message!=null && i-->0;)
836                 message=_extensions[i].rcvMeta(from, message);
837         }
838 
839         if (from!=null)
840         {
841             Extension[] client_exs = from.getExtensions();
842             if (client_exs!=null)
843             {
844                 for (int i=client_exs.length;message!=null && i-->0;)
845                     message=client_exs[i].rcvMeta(from, message);
846             }
847         }
848         return message;
849     }
850 
851     /* ------------------------------------------------------------ */
852     protected Message extendSendBayeux(Client from, Message message)
853     {
854         if (_extensions!=null)
855         {
856             for (int i=0;message!=null && i<_extensions.length;i++)
857             {
858                 message=_extensions[i].send(from, message);
859             }
860         }
861 
862         return message;
863     }
864 
865     /* ------------------------------------------------------------ */
866     public Message extendSendClient(Client from, ClientImpl to, Message message)
867     {
868         if (to!=null)
869         {
870             Extension[] client_exs = to.getExtensions();
871             if (client_exs!=null)
872             {
873                 for (int i=0;message!=null && i<client_exs.length;i++)
874                     message=client_exs[i].send(from, message);
875             }
876         }
877 
878         return message;
879     }
880 
881     /* ------------------------------------------------------------ */
882     public Message extendSendMeta(ClientImpl from, Message message)
883     {
884         if (_extensions!=null)
885         {
886             for (int i=0;message!=null && i<_extensions.length;i++)
887                 message=_extensions[i].sendMeta(from, message);
888         }
889 
890         if (from!=null)
891         {
892             Extension[] client_exs = from.getExtensions();
893             if (client_exs!=null)
894             {
895                 for (int i=0;message!=null && i<client_exs.length;i++)
896                     message=client_exs[i].sendMeta(from, message);
897             }
898         }
899 
900         return message;
901     }
902 
903 
904     /* ------------------------------------------------------------ */
905     /* ------------------------------------------------------------ */
906     public static class DefaultPolicy implements SecurityPolicy
907     {
908         public boolean canHandshake(Message message)
909         {
910             return true;
911         }
912 
913         public boolean canCreate(Client client, String channel, Message message)
914         {
915             return client!=null && !channel.startsWith(Bayeux.META_SLASH);
916         }
917 
918         public boolean canSubscribe(Client client, String channel, Message message)
919         {
920 	    if (client!=null && ("/**".equals(channel) || "/*".equals(channel)))
921 	        return false;
922             return client!=null && !channel.startsWith(Bayeux.META_SLASH);
923         }
924 
925         public boolean canPublish(Client client, String channel, Message message)
926         {
927             return client!=null || client==null && Bayeux.META_HANDSHAKE.equals(channel);
928         }
929 
930     }
931 
932 
933     /* ------------------------------------------------------------ */
934     /* ------------------------------------------------------------ */
935     protected abstract class Handler
936     {
937         abstract void handle(ClientImpl client, Transport transport, Message message) throws IOException;
938         abstract ChannelId getMetaChannelId();
939         void unknownClient(Transport transport,String channel) throws IOException
940         {
941             MessageImpl reply=newMessage();
942 
943             reply.put(CHANNEL_FIELD,channel);
944             reply.put(SUCCESSFUL_FIELD,Boolean.FALSE);
945             reply.put(ERROR_FIELD,"402::Unknown client");
946             reply.put("advice",_handshakeAdvice);
947             transport.send(reply);
948             reply.decRef();
949         }
950         
951         void sendMetaReply(final ClientImpl client,Message reply, final Transport transport) throws IOException
952         {
953             reply=extendSendMeta(client,reply);
954             if (reply!=null)
955             {
956                 transport.send(reply);
957                 if (reply instanceof MessageImpl)
958                     ((MessageImpl)reply).decRef();
959             }
960         }
961     }
962 
963     /* ------------------------------------------------------------ */
964     /* ------------------------------------------------------------ */
965     protected class ConnectHandler extends Handler
966     {
967         protected String _metaChannel=META_CONNECT;
968 
969         @Override
970         ChannelId getMetaChannelId()
971         {
972             return META_CONNECT_ID;
973         }
974 
975         @Override
976         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
977         {
978             if (client==null)
979             {
980                 unknownClient(transport,_metaChannel);
981                 return;
982             }
983 
984             // is this the first connect message?
985             String type=client.getConnectionType();
986             boolean polling=true;
987             if (type==null)
988             {
989                 type=(String)message.get(Bayeux.CONNECTION_TYPE_FIELD);
990                 client.setConnectionType(type);
991                 polling=false;
992             }
993 
994             Object advice = message.get(ADVICE_FIELD);
995             if (advice!=null)
996             {
997                 Long timeout=(Long)((Map)advice).get("timeout");
998                 if (timeout!=null && timeout.longValue()>0)
999                     client.setTimeout(timeout.longValue());
1000                 else
1001                     client.setTimeout(0);
1002             }
1003             else
1004                 client.setTimeout(0);
1005 
1006             advice=null;
1007 
1008             // Work out if multiple clients from some browser?
1009             if (polling && _multiFrameInterval>0 && client.getBrowserId()!=null)
1010             {
1011                 List<String> clients=clientsOnBrowser(client.getBrowserId());
1012                 int count=clients.size();
1013                 if (count>1)
1014                 {
1015                     polling=clients.get(0).equals(client.getId());
1016                     advice=client.getAdvice();
1017                     if (advice==null)
1018                         advice=_multiFrameAdvice;
1019                     else // could probably cache this
1020                         advice=multiFrameAdvice((JSON.Literal)advice);
1021                 }
1022             }
1023 
1024             synchronized(this)
1025             {
1026                 if (advice==null)
1027                 {
1028                     if (_adviceVersion!=client._adviseVersion)
1029                     {
1030                         advice=_advice;
1031                         client._adviseVersion=_adviceVersion;
1032                     }
1033                 }
1034                 else
1035                     client._adviseVersion=-1; // clear so it is reset after multi state clears
1036             }
1037 
1038             // reply to connect message
1039             String id=message.getId();
1040 
1041             Message reply=newMessage(message);
1042 
1043             reply.put(CHANNEL_FIELD,META_CONNECT);
1044             reply.put(SUCCESSFUL_FIELD,Boolean.TRUE);
1045             if (advice!=null)
1046                 reply.put(ADVICE_FIELD,advice);
1047             if (id!=null)
1048                 reply.put(ID_FIELD,id);
1049 
1050             if (polling)
1051                 transport.setMetaConnnectReply(reply);
1052             else
1053                 sendMetaReply(client,reply,transport);
1054         }
1055     }
1056 
1057     /* ------------------------------------------------------------ */
1058     /* ------------------------------------------------------------ */
1059     protected class DisconnectHandler extends Handler
1060     {
1061         @Override
1062         ChannelId getMetaChannelId()
1063         {
1064             return META_DISCONNECT_ID;
1065         }
1066 
1067         @Override
1068         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
1069         {
1070             if (client==null)
1071             {
1072                 unknownClient(transport,META_DISCONNECT);
1073                 return;
1074             }
1075             if (isLogInfo())
1076                 logInfo("Disconnect "+client.getId());
1077 
1078             client.remove(false);
1079 
1080             Message reply=newMessage(message);
1081             reply.put(CHANNEL_FIELD,META_DISCONNECT);
1082             reply.put(SUCCESSFUL_FIELD,Boolean.TRUE);
1083             String id=message.getId();
1084             if (id!=null)
1085                 reply.put(ID_FIELD,id);
1086 
1087             reply=extendSendMeta(client,reply);
1088 
1089             Message pollReply = transport.getMetaConnectReply();
1090             if (pollReply!=null)
1091             {
1092                 transport.setMetaConnnectReply(null);
1093                 sendMetaReply(client,pollReply,transport);
1094             }
1095             sendMetaReply(client,reply,transport);
1096         }
1097     }
1098 
1099 
1100     /* ------------------------------------------------------------ */
1101     /* ------------------------------------------------------------ */
1102     protected class HandshakeHandler extends Handler
1103     {
1104         @Override
1105         ChannelId getMetaChannelId()
1106         {
1107             return META_HANDSHAKE_ID;
1108         }
1109 
1110         @Override
1111         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
1112         {
1113             if (client!=null)
1114                 throw new IllegalStateException();
1115 
1116             if (_securityPolicy!=null && !_securityPolicy.canHandshake(message))
1117             {
1118                 Message reply=newMessage(message);
1119                 reply.put(CHANNEL_FIELD,META_HANDSHAKE);
1120                 reply.put(SUCCESSFUL_FIELD,Boolean.FALSE);
1121                 reply.put(ERROR_FIELD,"403::Handshake denied");
1122 
1123                 sendMetaReply(client,reply,transport);
1124                 return;
1125             }
1126 
1127             client=newRemoteClient();
1128 
1129             Message reply=newMessage(message);
1130             reply.put(CHANNEL_FIELD, META_HANDSHAKE);
1131             reply.put(VERSION_FIELD, "1.0");
1132             reply.put(MIN_VERSION_FIELD, "0.9");
1133 
1134             if (client!=null)
1135             {
1136                 reply.put(SUPP_CONNECTION_TYPE_FIELD, _transports);
1137                 reply.put(SUCCESSFUL_FIELD, Boolean.TRUE);
1138                 reply.put(CLIENT_FIELD, client.getId());
1139                 if (_advice!=null)
1140                     reply.put(ADVICE_FIELD,_advice);
1141             }
1142             else
1143             {
1144                 reply.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
1145                 if (_advice!=null)
1146                     reply.put(ADVICE_FIELD,_advice);
1147             }
1148 
1149             if (isLogDebug())
1150                 logDebug("handshake.handle: reply="+reply);
1151 
1152             String id=message.getId();
1153             if (id!=null)
1154                 reply.put(ID_FIELD,id);
1155 
1156             sendMetaReply(client,reply,transport);
1157         }
1158     }
1159 
1160     /* ------------------------------------------------------------ */
1161     /* ------------------------------------------------------------ */
1162     protected class PublishHandler extends Handler
1163     {
1164         @Override
1165         ChannelId getMetaChannelId()
1166         {
1167             return null;
1168         }
1169 
1170         @Override
1171         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
1172         {
1173             String channel_id=message.getChannel();
1174 
1175             if (client==null && message.containsKey(CLIENT_FIELD))
1176             {
1177                 unknownClient(transport,channel_id);
1178                 return;
1179             }
1180 
1181             String id=message.getId();
1182 
1183             ChannelId cid=getChannelId(channel_id);
1184             Object data=message.get(Bayeux.DATA_FIELD);
1185 
1186             Message reply=newMessage(message);
1187             reply.put(CHANNEL_FIELD,channel_id);
1188             if (id!=null)
1189                 reply.put(ID_FIELD,id);
1190 
1191             if (data!=null&&_securityPolicy.canPublish(client,channel_id,message))
1192             {
1193                 message.remove(CLIENT_FIELD);
1194                 message=extendSendBayeux(client,message);
1195 
1196                 if (message!=null)
1197                 {
1198                     reply.put(SUCCESSFUL_FIELD,Boolean.TRUE);
1199                 }
1200                 else
1201                 {
1202                     reply.put(SUCCESSFUL_FIELD,Boolean.FALSE);
1203                     reply.put(ERROR_FIELD,"404::Message deleted");
1204                 }
1205             }
1206             else
1207             {
1208                 message=null;
1209                 reply.put(SUCCESSFUL_FIELD,Boolean.FALSE);
1210                 reply.put(ERROR_FIELD,"403::Publish denied");
1211             }
1212 
1213             sendMetaReply(client,reply,transport);
1214 
1215             if (message!=null)
1216                 _root.doDelivery(cid,client,message);
1217         }
1218     }
1219 
1220     /* ------------------------------------------------------------ */
1221     /* ------------------------------------------------------------ */
1222     protected class MetaPublishHandler extends Handler
1223     {
1224         @Override
1225         ChannelId getMetaChannelId()
1226         {
1227             return null;
1228         }
1229 
1230         @Override
1231         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
1232         {
1233             String channel_id=message.getChannel();
1234 
1235             if (client==null && !META_HANDSHAKE.equals(channel_id))
1236             {
1237                 // unknown client
1238                 return;
1239             }
1240 
1241             if(_securityPolicy.canPublish(client,channel_id,message))
1242             {
1243                 _root.doDelivery(getChannelId(channel_id),client,message);
1244             }
1245         }
1246     }
1247 
1248     /* ------------------------------------------------------------ */
1249     /* ------------------------------------------------------------ */
1250     protected class SubscribeHandler extends Handler
1251     {
1252         @Override
1253         ChannelId getMetaChannelId()
1254         {
1255             return META_SUBSCRIBE_ID;
1256         }
1257 
1258         @Override
1259         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
1260         {
1261             if (client==null)
1262             {
1263                 unknownClient(transport,META_SUBSCRIBE);
1264                 return;
1265             }
1266 
1267             String subscribe_id=(String)message.get(SUBSCRIPTION_FIELD);
1268 
1269             // select a random channel ID if none specifified
1270             if (subscribe_id==null)
1271             {
1272                 subscribe_id=Long.toString(getRandom(),36);
1273                 while (getChannel(subscribe_id)!=null)
1274                     subscribe_id=Long.toString(getRandom(),36);
1275             }
1276 
1277             ChannelId cid=null;
1278             boolean can_subscribe=false;
1279 
1280             if (subscribe_id.startsWith(Bayeux.SERVICE_SLASH))
1281             {
1282                 can_subscribe=true;
1283             }
1284             else if (subscribe_id.startsWith(Bayeux.META_SLASH))
1285             {
1286                 can_subscribe=false;
1287             }
1288             else
1289             {
1290                 cid=getChannelId(subscribe_id);
1291                 can_subscribe=_securityPolicy.canSubscribe(client,subscribe_id,message);
1292             }
1293 
1294             Message reply=newMessage(message);
1295             reply.put(CHANNEL_FIELD,META_SUBSCRIBE);
1296             reply.put(SUBSCRIPTION_FIELD,subscribe_id);
1297 
1298             if (can_subscribe)
1299             {
1300                 if (cid!=null)
1301                 {
1302                     ChannelImpl channel=getChannel(cid);
1303                     if (channel==null&&_securityPolicy.canCreate(client,subscribe_id,message))
1304                         channel=(ChannelImpl)getChannel(subscribe_id, true);
1305 
1306                     if (channel!=null)
1307                         channel.subscribe(client);
1308                     else
1309                         can_subscribe=false;
1310                 }
1311 
1312                 if (can_subscribe)
1313                 {
1314                     reply.put(SUCCESSFUL_FIELD,Boolean.TRUE);
1315                 }
1316                 else
1317                 {
1318                     reply.put(SUCCESSFUL_FIELD,Boolean.FALSE);
1319                     reply.put(ERROR_FIELD,"403::cannot create");
1320                 }
1321             }
1322             else
1323             {
1324                 reply.put(SUCCESSFUL_FIELD,Boolean.FALSE);
1325                 reply.put(ERROR_FIELD,"403::cannot subscribe");
1326 
1327             }
1328 
1329             String id=message.getId();
1330             if (id!=null)
1331                 reply.put(ID_FIELD,id);
1332 
1333             sendMetaReply(client,reply,transport);
1334         }
1335     }
1336 
1337     /* ------------------------------------------------------------ */
1338     /* ------------------------------------------------------------ */
1339     protected class UnsubscribeHandler extends Handler
1340     {
1341         @Override
1342         ChannelId getMetaChannelId()
1343         {
1344             return META_UNSUBSCRIBE_ID;
1345         }
1346 
1347         @Override
1348         public void handle(ClientImpl client, Transport transport, Message message) throws IOException
1349         {
1350             if (client==null)
1351             {
1352                 unknownClient(transport,META_UNSUBSCRIBE);
1353                 return;
1354             }
1355 
1356             String channel_id=(String)message.get(SUBSCRIPTION_FIELD);
1357             ChannelImpl channel=getChannel(channel_id);
1358             if (channel!=null)
1359                 channel.unsubscribe(client);
1360 
1361             Message reply=newMessage(message);
1362             reply.put(CHANNEL_FIELD,META_UNSUBSCRIBE);
1363             reply.put(SUBSCRIPTION_FIELD,channel_id);
1364             reply.put(SUCCESSFUL_FIELD,Boolean.TRUE);
1365 
1366             String id=message.getId();
1367             if (id!=null)
1368                 reply.put(ID_FIELD,id);
1369 
1370             sendMetaReply(client,reply,transport);
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             sendMetaReply(client,reply,transport);
1396         }
1397     }
1398 
1399 
1400     /* ------------------------------------------------------------ */
1401     /* ------------------------------------------------------------ */
1402     protected class ServiceChannel extends ChannelImpl
1403     {
1404         ServiceChannel(String id)
1405         {
1406             super(id,AbstractBayeux.this);
1407         }
1408 
1409         /* ------------------------------------------------------------ */
1410         /* (non-Javadoc)
1411          * @see org.mortbay.cometd.ChannelImpl#addChild(org.mortbay.cometd.ChannelImpl)
1412          */
1413         @Override
1414         public void addChild(ChannelImpl channel)
1415         {
1416             super.addChild(channel);
1417             setPersistent(true);
1418         }
1419 
1420         /* ------------------------------------------------------------ */
1421         @Override
1422         public void subscribe(Client client)
1423         {
1424             if (client.isLocal())
1425                 super.subscribe(client);
1426         }
1427 
1428     }
1429 }