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