// ======================================================================== // Copyright 2006 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //======================================================================== package org.mortbay.cometd; import java.io.IOException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import org.cometd.Bayeux; import org.cometd.BayeuxListener; import org.cometd.Channel; import org.cometd.ChannelBayeuxListener; import org.cometd.Client; import org.cometd.ClientBayeuxListener; import org.cometd.Extension; import org.cometd.Message; import org.cometd.SecurityPolicy; import org.mortbay.util.ajax.JSON; /* ------------------------------------------------------------ */ /** * @author gregw * @author aabeling: added JSONP transport * */ public abstract class AbstractBayeux extends MessagePool implements Bayeux { public static final ChannelId META_ID=new ChannelId(META); public static final ChannelId META_CONNECT_ID=new ChannelId(META_CONNECT); public static final ChannelId META_CLIENT_ID=new ChannelId(META_CLIENT); public static final ChannelId META_DISCONNECT_ID=new ChannelId(META_DISCONNECT); public static final ChannelId META_HANDSHAKE_ID=new ChannelId(META_HANDSHAKE); public static final ChannelId META_PING_ID=new ChannelId(META_PING); public static final ChannelId META_STATUS_ID=new ChannelId(META_STATUS); public static final ChannelId META_SUBSCRIBE_ID=new ChannelId(META_SUBSCRIBE); public static final ChannelId META_UNSUBSCRIBE_ID=new ChannelId(META_UNSUBSCRIBE); private static final Map<String,Object> EXT_JSON_COMMENTED=new HashMap<String,Object>(2){ { this.put("json-comment-filtered",Boolean.TRUE); } }; private HashMap<String,Handler> _handlers=new HashMap<String,Handler>(); private ChannelImpl _root = new ChannelImpl("/",this); private ConcurrentHashMap<String,ClientImpl> _clients=new ConcurrentHashMap<String,ClientImpl>(); protected SecurityPolicy _securityPolicy=new DefaultPolicy(); protected JSON.Literal _advice; protected JSON.Literal _multiFrameAdvice; protected int _adviceVersion=0; protected Object _handshakeAdvice=new JSON.Literal("{\"reconnect\":\"handshake\",\"interval\":500}"); protected int _logLevel; protected long _timeout=240000; protected long _interval=0; protected long _maxInterval=30000; protected boolean _JSONCommented; protected boolean _initialized; protected ConcurrentHashMap<String, List<String>> _browser2client=new ConcurrentHashMap<String, List<String>>(); protected int _multiFrameInterval=-1; protected boolean _directDeliver=true; protected boolean _requestAvailable; protected ThreadLocal<HttpServletRequest> _request = new ThreadLocal<HttpServletRequest>(); transient ServletContext _context; transient Random _random; transient ConcurrentHashMap<String, ChannelId> _channelIdCache; protected Handler _publishHandler; protected Handler _metaPublishHandler; protected int _maxClientQueue=-1; protected List<Extension> _extensions=new CopyOnWriteArrayList<Extension>(); protected JSON.Literal _transports=new JSON.Literal("[\""+Bayeux.TRANSPORT_LONG_POLL+ "\",\""+Bayeux.TRANSPORT_CALLBACK_POLL+"\"]"); protected List<ClientBayeuxListener> _clientListeners=new CopyOnWriteArrayList<ClientBayeuxListener>(); protected List<ChannelBayeuxListener> _channelListeners=new CopyOnWriteArrayList<ChannelBayeuxListener>(); /* ------------------------------------------------------------ */ /** * @param context. * The logLevel init parameter is used to set the logging to * 0=none, 1=info, 2=debug */ protected AbstractBayeux() { _publishHandler=new PublishHandler(); _metaPublishHandler=new MetaPublishHandler(); _handlers.put(META_HANDSHAKE,new HandshakeHandler()); _handlers.put(META_CONNECT,new ConnectHandler()); _handlers.put(META_DISCONNECT,new DisconnectHandler()); _handlers.put(META_SUBSCRIBE,new SubscribeHandler()); _handlers.put(META_UNSUBSCRIBE,new UnsubscribeHandler()); _handlers.put(META_PING,new PingHandler()); setTimeout(getTimeout()); } /* ------------------------------------------------------------ */ public void addExtension(Extension ext) { _extensions.add(ext); } /* ------------------------------------------------------------ */ public List<Extension> getExtensions() { // TODO - remove this hack of a method! return _extensions; } /* ------------------------------------------------------------ */ public void removeExtension(Extension ext) { _extensions.remove(ext); } /* ------------------------------------------------------------ */ /** * @param id * @return */ public ChannelImpl getChannel(ChannelId id) { return _root.getChild(id); } /* ------------------------------------------------------------ */ public ChannelImpl getChannel(String id) { ChannelId cid=getChannelId(id); if (cid.depth()==0) return null; return _root.getChild(cid); } /* ------------------------------------------------------------ */ public Channel getChannel(String id, boolean create) { synchronized(this) { ChannelImpl channel=getChannel(id); if (channel==null && create) { channel=new ChannelImpl(id,this); _root.addChild(channel); if (isLogInfo()) logInfo("newChannel: "+channel); } return channel; } } /* ------------------------------------------------------------ */ public ChannelId getChannelId(String id) { ChannelId cid = _channelIdCache.get(id); if (cid==null) { // TODO shrink cache! cid=new ChannelId(id); _channelIdCache.put(id,cid); } return cid; } /* ------------------------------------------------------------ */ /* (non-Javadoc) * @see org.mortbay.cometd.Bx#getClient(java.lang.String) */ public Client getClient(String client_id) { synchronized(this) { if (client_id==null) return null; Client client = _clients.get(client_id); return client; } } /* ------------------------------------------------------------ */ public Set<String> getClientIDs() { return _clients.keySet(); } /* ------------------------------------------------------------ */ /** * @return The maximum time in ms to wait between polls before timing out a client */ public long getMaxInterval() { return _maxInterval; } /* ------------------------------------------------------------ */ /** * @return the logLevel. 0=none, 1=info, 2=debug */ public int getLogLevel() { return _logLevel; } /* ------------------------------------------------------------ */ /* (non-Javadoc) * @see org.mortbay.cometd.Bx#getSecurityPolicy() */ public SecurityPolicy getSecurityPolicy() { return _securityPolicy; } /* ------------------------------------------------------------ */ public long getTimeout() { return _timeout; } /* ------------------------------------------------------------ */ public long getInterval() { return _interval; } /* ------------------------------------------------------------ */ /** * @return true if published messages are directly delivered to subscribers. False if * a new message is to be created that holds only supported fields. */ public boolean isDirectDeliver() { return _directDeliver; } /* ------------------------------------------------------------ */ /** * @param directDeliver true if published messages are directly delivered to subscribers. False if * a new message is to be created that holds only supported fields. */ public void setDirectDeliver(boolean directDeliver) { _directDeliver = directDeliver; } /* ------------------------------------------------------------ */ /** Handle a Bayeux message. * This is normally only called by the bayeux servlet or a test harness. * @param client The client if known * @param transport The transport to use for the message * @param message The bayeux message. */ public String handle(ClientImpl client, Transport transport, Message message) throws IOException { String channel_id=message.getChannel(); Handler handler=(Handler)_handlers.get(channel_id); if (handler!=null) { // known meta channel ListIterator<Extension> iter = _extensions.listIterator(_extensions.size()); while(iter.hasPrevious()) message=iter.previous().rcvMeta(message); handler.handle(client,transport,message); _metaPublishHandler.handle(client,transport,message); } else if (channel_id.startsWith(META_SLASH)) { // unknown meta channel ListIterator<Extension> iter = _extensions.listIterator(_extensions.size()); while(iter.hasPrevious()) message=iter.previous().rcvMeta(message); _metaPublishHandler.handle(client,transport,message); } else { // non meta channel handler=_publishHandler; ListIterator<Extension> iter = _extensions.listIterator(_extensions.size()); while(iter.hasPrevious()) message=iter.previous().rcv(message); handler.handle(client,transport,message); } return channel_id; } /* ------------------------------------------------------------ */ public boolean hasChannel(String id) { ChannelId cid=getChannelId(id); return _root.getChild(cid)!=null; } /* ------------------------------------------------------------ */ public boolean isInitialized() { return _initialized; } /* ------------------------------------------------------------ */ /** * @return the commented */ public boolean isJSONCommented() { return _JSONCommented; } /* ------------------------------------------------------------ */ public boolean isLogDebug() { return _logLevel>1; } /* ------------------------------------------------------------ */ public boolean isLogInfo() { return _logLevel>0; } /* ------------------------------------------------------------ */ public void logDebug(String message) { if (_logLevel>1) _context.log(message); } /* ------------------------------------------------------------ */ public void logDebug(String message, Throwable th) { if (_logLevel>1) _context.log(message,th); } /* ------------------------------------------------------------ */ public void logWarn(String message, Throwable th) { _context.log(message+": "+th.toString()); } /* ------------------------------------------------------------ */ public void logWarn(String message) { _context.log(message); } /* ------------------------------------------------------------ */ public void logInfo(String message) { if (_logLevel>0) _context.log(message); } /* ------------------------------------------------------------ */ public Client newClient(String idPrefix) { ClientImpl client = new ClientImpl(this,idPrefix); return client; } /* ------------------------------------------------------------ */ public abstract ClientImpl newRemoteClient(); /* ------------------------------------------------------------ */ /** Create new transport object for a bayeux message * @param client The client * @param message the bayeux message * @return the negotiated transport. */ public Transport newTransport(ClientImpl client, Map<?,?> message) { if (isLogDebug()) logDebug("newTransport: client="+client+",message="+message); Transport result=null; try { String type=client==null?null:client.getConnectionType(); if (type==null) type=(String)message.get(Bayeux.CONNECTION_TYPE_FIELD); if (Bayeux.TRANSPORT_CALLBACK_POLL.equals(type) || type==null) { String jsonp=(String)message.get(Bayeux.JSONP_PARAMETER); if(jsonp!=null) result=new JSONPTransport(client!=null&&client.isJSONCommented(),jsonp); else result=new JSONTransport(client!=null&&client.isJSONCommented()); } else result=new JSONTransport(client!=null&&client.isJSONCommented()); } catch (Exception e) { throw new RuntimeException(e); } if (isLogDebug()) logDebug("newTransport: result="+result); return result; } /* ------------------------------------------------------------ */ /** Publish data to a channel. * Creates a message and delivers it to the root channel. * @param to * @param from * @param data * @param msgId */ protected void doPublish(ChannelId to, Client from, Object data, String msgId) { Message msg = newMessage(); msg.put(CHANNEL_FIELD,to.toString()); if (msgId==null) { long id=msg.hashCode() ^(to==null?0:to.hashCode()) ^(from==null?0:from.hashCode()); id=id<0?-id:id; msg.put(ID_FIELD,Long.toString(id,36)); } else msg.put(ID_FIELD,msgId); msg.put(DATA_FIELD,data); for (Extension e:_extensions) msg=e.send(msg); _root.doDelivery(to,from,msg); ((MessageImpl)msg).decRef(); } /* ------------------------------------------------------------ */ public boolean removeChannel(ChannelImpl channel) { boolean removed = _root.doRemove(channel); if (removed) for (ChannelBayeuxListener l : _channelListeners) l.channelRemoved(channel); return removed; } /* ------------------------------------------------------------ */ public void addChannel(ChannelImpl channel) { for (ChannelBayeuxListener l : _channelListeners) l.channelAdded(channel); } /* ------------------------------------------------------------ */ protected String newClientId(long variation, String idPrefix) { if (idPrefix==null) return Long.toString(getRandom(),36)+Long.toString(variation,36); else return idPrefix+"_"+Long.toString(getRandom(),36); } /* ------------------------------------------------------------ */ protected void addClient(ClientImpl client,String idPrefix) { while(true) { String id = newClientId(client.hashCode(),idPrefix); client.setId(id); ClientImpl other = _clients.putIfAbsent(id,client); if (other==null) { for (ClientBayeuxListener l : _clientListeners) l.clientAdded((Client)client); return; } } } /* ------------------------------------------------------------ */ /* (non-Javadoc) * @see org.mortbay.cometd.Bx#removeClient(java.lang.String) */ public Client removeClient(String client_id) { ClientImpl client; synchronized(this) { if (client_id==null) return null; client = _clients.remove(client_id); } if (client!=null) { client.unsubscribeAll(); for (ClientBayeuxListener l : _clientListeners) l.clientRemoved((Client)client); } return client; } /* ------------------------------------------------------------ */ /** * @param ms The maximum time in ms to wait between polls before timing out a client */ public void setMaxInterval(long ms) { _maxInterval=ms; } /* ------------------------------------------------------------ */ /** * @param commented the commented to set */ public void setJSONCommented(boolean commented) { _JSONCommented=commented; } /* ------------------------------------------------------------ */ /** * @param logLevel * the logLevel: 0=none, 1=info, 2=debug */ public void setLogLevel(int logLevel) { _logLevel=logLevel; } /* ------------------------------------------------------------ */ /* (non-Javadoc) * @see org.mortbay.cometd.Bx#setSecurityPolicy(org.mortbay.cometd.SecurityPolicy) */ public void setSecurityPolicy(SecurityPolicy securityPolicy) { _securityPolicy=securityPolicy; } /* ------------------------------------------------------------ */ public void setTimeout(long ms) { _timeout = ms; generateAdvice(); } /* ------------------------------------------------------------ */ public void setInterval(long ms) { _interval = ms; generateAdvice(); } /* ------------------------------------------------------------ */ /** * The time a client should delay between reconnects when multiple * connections from the same browser are detected. This effectively * produces traditional polling. * @param multiFrameInterval the multiFrameInterval to set */ public void setMultiFrameInterval(int multiFrameInterval) { _multiFrameInterval=multiFrameInterval; generateAdvice(); } /* ------------------------------------------------------------ */ /** * @return the multiFrameInterval in milliseconds */ public int getMultiFrameInterval() { return _multiFrameInterval; } /* ------------------------------------------------------------ */ void generateAdvice() { setAdvice(new JSON.Literal("{\"reconnect\":\"retry\",\"interval\":"+getInterval()+",\"timeout\":"+getTimeout()+"}")); } /* ------------------------------------------------------------ */ public void setAdvice(JSON.Literal advice) { synchronized(this) { _adviceVersion++; _advice=advice; _multiFrameAdvice=new JSON.Literal(JSON.toString(multiFrameAdvice(advice))); } } /* ------------------------------------------------------------ */ private Map<String,Object> multiFrameAdvice(JSON.Literal advice) { Map<String,Object> a = (Map<String,Object>)JSON.parse(_advice.toString()); a.put("multiple-clients",Boolean.TRUE); if (_multiFrameInterval>0) { a.put("reconnect","retry"); a.put("interval",_multiFrameInterval); } else a.put("reconnect","none"); return a; } /* ------------------------------------------------------------ */ public JSON.Literal getAdvice() { return _advice; } /* ------------------------------------------------------------ */ /** * @return TRUE if {@link #getCurrentRequest()} will return the current request */ public boolean isRequestAvailable() { return _requestAvailable; } /* ------------------------------------------------------------ */ /** * @param requestAvailable TRUE if {@link #getCurrentRequest()} will return the current request */ public void setRequestAvailable(boolean requestAvailable) { _requestAvailable=requestAvailable; } /* ------------------------------------------------------------ */ /** * @return the current request if {@link #isRequestAvailable()} is true, else null */ public HttpServletRequest getCurrentRequest() { return _request.get(); } /* ------------------------------------------------------------ */ /** * @return the current request if {@link #isRequestAvailable()} is true, else null */ void setCurrentRequest(HttpServletRequest request) { _request.set(request); } /* ------------------------------------------------------------ */ public Collection<Channel> getChannels() { List<Channel> channels = new ArrayList<Channel>(); _root.getChannels(channels); return channels; } /* ------------------------------------------------------------ */ /** * @return */ public int getChannelCount() { return _root.getChannelCount(); } /* ------------------------------------------------------------ */ public Collection<Client> getClients() { synchronized(this) { return new ArrayList<Client>(_clients.values()); } } /* ------------------------------------------------------------ */ /** * @return */ public int getClientCount() { synchronized(this) { return _clients.size(); } } /* ------------------------------------------------------------ */ public boolean hasClient(String clientId) { synchronized(this) { if (clientId==null) return false; return _clients.containsKey(clientId); } } /* ------------------------------------------------------------ */ public Channel removeChannel(String channelId) { Channel channel = getChannel(channelId); boolean removed = false; if (channel!=null) removed = channel.remove(); if (removed) return channel; else return null; } /* ------------------------------------------------------------ */ protected void initialize(ServletContext context) { synchronized(this) { _initialized=true; _context=context; try { _random=SecureRandom.getInstance("SHA1PRNG"); } catch (Exception e) { context.log("Could not get secure random for ID generation",e); _random=new Random(); } _random.setSeed(_random.nextLong()^hashCode()^(context.hashCode()<<32)^Runtime.getRuntime().freeMemory()); _channelIdCache=new ConcurrentHashMap<String, ChannelId>(); _root.addChild(new ServiceChannel(Bayeux.SERVICE)); } } /* ------------------------------------------------------------ */ long getRandom() { long l=_random.nextLong(); return l<0?-l:l; } /* ------------------------------------------------------------ */ void clientOnBrowser(String browserId,String clientId) { List<String> clients=_browser2client.get(browserId); if (clients==null) { List<String> new_clients=new CopyOnWriteArrayList<String>(); clients=_browser2client.putIfAbsent(browserId,new_clients); if (clients==null) clients=new_clients; } clients.add(clientId); } /* ------------------------------------------------------------ */ void clientOffBrowser(String browserId,String clientId) { List<String> clients=_browser2client.get(browserId); if (clients!=null) clients.remove(clientId); } /* ------------------------------------------------------------ */ List<String> clientsOnBrowser(String browserId) { List<String> clients=_browser2client.get(browserId); if (clients==null) return Collections.emptyList(); return clients; } /* ------------------------------------------------------------ */ public void addListener(BayeuxListener listener) { if (listener instanceof ClientBayeuxListener) _clientListeners.add((ClientBayeuxListener)listener); else if(listener instanceof ChannelBayeuxListener) _channelListeners.add((ChannelBayeuxListener)listener); } /* ------------------------------------------------------------ */ public int getMaxClientQueue() { return _maxClientQueue; } /* ------------------------------------------------------------ */ public void setMaxClientQueue(int size) { _maxClientQueue=size; } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ public static class DefaultPolicy implements SecurityPolicy { public boolean canHandshake(Message message) { return true; } public boolean canCreate(Client client, String channel, Message message) { return client!=null && !channel.startsWith(Bayeux.META_SLASH); } public boolean canSubscribe(Client client, String channel, Message message) { if (client!=null && ("/**".equals(channel) || "/*".equals(channel))) return false; return client!=null && !channel.startsWith(Bayeux.META_SLASH); } public boolean canPublish(Client client, String channel, Message message) { return client!=null || client==null && Bayeux.META_HANDSHAKE.equals(channel); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ protected abstract class Handler { abstract void handle(ClientImpl client, Transport transport, Message message) throws IOException; abstract ChannelId getMetaChannelId(); void unknownClient(Transport transport,String channel) throws IOException { MessageImpl reply=newMessage(); reply.put(CHANNEL_FIELD,channel); reply.put(SUCCESSFUL_FIELD,Boolean.FALSE); reply.put(ERROR_FIELD,"402::Unknown client"); reply.put("advice",_handshakeAdvice); transport.send(reply); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ protected class ConnectHandler extends Handler { protected String _metaChannel=META_CONNECT; @Override ChannelId getMetaChannelId() { return META_CONNECT_ID; } @Override public void handle(ClientImpl client, Transport transport, Message message) throws IOException { if (client==null) { unknownClient(transport,_metaChannel); return; } // is this the first connect message? String type=client.getConnectionType(); boolean polling=true; if (type==null) { type=(String)message.get(Bayeux.CONNECTION_TYPE_FIELD); client.setConnectionType(type); polling=false; } Object advice = message.get(ADVICE_FIELD); if (advice!=null) { Long timeout=(Long)((Map)advice).get("timeout"); if (timeout!=null && timeout.longValue()>0) client.setTimeout(timeout.longValue()); else client.setTimeout(0); } else client.setTimeout(0); advice=null; // Work out if multiple clients from some browser? if (polling && _multiFrameInterval>0 && client.getBrowserId()!=null) { List<String> clients=clientsOnBrowser(client.getBrowserId()); int count=clients.size(); if (count>1) { polling=clients.get(0).equals(client.getId()); advice=client.getAdvice(); if (advice==null) advice=_multiFrameAdvice; else // could probably cache this advice=multiFrameAdvice((JSON.Literal)advice); } } synchronized(this) { if (advice==null) { if (_adviceVersion!=client._adviseVersion) { advice=_advice; client._adviseVersion=_adviceVersion; } } else client._adviseVersion=-1; // clear so it is reset after multi state clears } // reply to connect message String id=message.getId(); Message reply=newMessage(message); reply.put(CHANNEL_FIELD,META_CONNECT); reply.put(SUCCESSFUL_FIELD,Boolean.TRUE); if (advice!=null) reply.put(ADVICE_FIELD,advice); if (id!=null) reply.put(ID_FIELD,id); if (polling) transport.setPollReply(reply); else { for (Extension e:_extensions) reply=e.sendMeta(reply); transport.send(reply); } } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ protected class DisconnectHandler extends Handler { @Override ChannelId getMetaChannelId() { return META_DISCONNECT_ID; } @Override public void handle(ClientImpl client, Transport transport, Message message) throws IOException { if (client==null) { unknownClient(transport,META_DISCONNECT); return; } if (isLogInfo()) logInfo("Disconnect "+client.getId()); client.remove(false); Message reply=newMessage(message); reply.put(CHANNEL_FIELD,META_DISCONNECT); reply.put(SUCCESSFUL_FIELD,Boolean.TRUE); String id=message.getId(); if (id!=null) reply.put(ID_FIELD,id); for (Extension e:_extensions) reply=e.sendMeta(reply); Message pollReply = transport.getPollReply(); if (pollReply!=null) { for (Extension e:_extensions) pollReply=e.sendMeta(pollReply); transport.send(pollReply); transport.setPollReply(null); } transport.send(reply); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ protected class HandshakeHandler extends Handler { @Override ChannelId getMetaChannelId() { return META_HANDSHAKE_ID; } @Override public void handle(ClientImpl client, Transport transport, Message message) throws IOException { if (client!=null) throw new IllegalStateException(); if (_securityPolicy!=null && !_securityPolicy.canHandshake(message)) { Message reply=newMessage(message); reply.put(CHANNEL_FIELD,META_HANDSHAKE); reply.put(SUCCESSFUL_FIELD,Boolean.FALSE); reply.put(ERROR_FIELD,"403::Handshake denied"); for (Extension e:_extensions) reply=e.sendMeta(reply); transport.send(reply); return; } client=newRemoteClient(); Map<?,?> ext = (Map<?,?>)message.get(EXT_FIELD); boolean commented=_JSONCommented && ext!=null && Boolean.TRUE.equals(ext.get("json-comment-filtered")); Message reply=newMessage(message); reply.put(CHANNEL_FIELD,META_HANDSHAKE); reply.put("version","1.0"); reply.put("minimumVersion","0.9"); if (isJSONCommented()) reply.put(EXT_FIELD,EXT_JSON_COMMENTED); if (client!=null) { reply.put("supportedConnectionTypes",_transports); reply.put("successful",Boolean.TRUE); reply.put(CLIENT_FIELD,client.getId()); if (_advice!=null) reply.put(ADVICE_FIELD,_advice); client.setJSONCommented(commented); transport.setJSONCommented(commented); } else { reply.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE); if (_advice!=null) reply.put(ADVICE_FIELD,_advice); } if (isLogDebug()) logDebug("handshake.handle: reply="+reply); String id=message.getId(); if (id!=null) reply.put(ID_FIELD,id); for (Extension e:_extensions) reply=e.sendMeta(reply); transport.send(reply); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ protected class PublishHandler extends Handler { @Override ChannelId getMetaChannelId() { return null; } @Override public void handle(ClientImpl client, Transport transport, Message message) throws IOException { String channel_id=message.getChannel(); if (client==null && message.containsKey(CLIENT_FIELD)) { unknownClient(transport,channel_id); return; } String id=message.getId(); ChannelId cid=getChannelId(channel_id); Object data=message.get(Bayeux.DATA_FIELD); Message reply=newMessage(message); reply.put(CHANNEL_FIELD,channel_id); if (id!=null) reply.put(ID_FIELD,id); if (data!=null&&_securityPolicy.canPublish(client,channel_id,message)) { reply.put(SUCCESSFUL_FIELD,Boolean.TRUE); for (Extension e:_extensions) reply=e.sendMeta(reply); transport.send(reply); if (_directDeliver) { message.remove(CLIENT_FIELD); for (Extension e:_extensions) message=e.send(message); _root.doDelivery(cid,client,message); } else doPublish(cid,client,data,id==null?null:id); } else { reply.put(SUCCESSFUL_FIELD,Boolean.FALSE); reply.put(ERROR_FIELD,"403::Publish denied"); for (Extension e:_extensions) reply=e.sendMeta(reply); transport.send(reply); } } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ protected class MetaPublishHandler extends Handler { @Override ChannelId getMetaChannelId() { return null; } @Override public void handle(ClientImpl client, Transport transport, Message message) throws IOException { String channel_id=message.getChannel(); if (client==null && !META_HANDSHAKE.equals(channel_id)) { // unknown client return; } if(_securityPolicy.canPublish(client,channel_id,message)) { _root.doDelivery(getChannelId(channel_id),client,message); } } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ protected class SubscribeHandler extends Handler { @Override ChannelId getMetaChannelId() { return META_SUBSCRIBE_ID; } @Override public void handle(ClientImpl client, Transport transport, Message message) throws IOException { if (client==null) { unknownClient(transport,META_SUBSCRIBE); return; } String subscribe_id=(String)message.get(SUBSCRIPTION_FIELD); // select a random channel ID if none specifified if (subscribe_id==null) { subscribe_id=Long.toString(getRandom(),36); while (getChannel(subscribe_id)!=null) subscribe_id=Long.toString(getRandom(),36); } ChannelId cid=null; boolean can_subscribe=false; if (subscribe_id.startsWith(Bayeux.SERVICE_SLASH)) { can_subscribe=true; } else if (subscribe_id.startsWith(Bayeux.META_SLASH)) { can_subscribe=false; } else { cid=getChannelId(subscribe_id); can_subscribe=_securityPolicy.canSubscribe(client,subscribe_id,message); } Message reply=newMessage(message); reply.put(CHANNEL_FIELD,META_SUBSCRIBE); reply.put(SUBSCRIPTION_FIELD,subscribe_id); if (can_subscribe) { if (cid!=null) { ChannelImpl channel=getChannel(cid); if (channel==null&&_securityPolicy.canCreate(client,subscribe_id,message)) channel=(ChannelImpl)getChannel(subscribe_id, true); if (channel!=null) channel.subscribe(client); else can_subscribe=false; } if (can_subscribe) { reply.put(SUCCESSFUL_FIELD,Boolean.TRUE); } else { reply.put(SUCCESSFUL_FIELD,Boolean.FALSE); reply.put(ERROR_FIELD,"403::cannot create"); } } else { reply.put(SUCCESSFUL_FIELD,Boolean.FALSE); reply.put(ERROR_FIELD,"403::cannot subscribe"); } String id=message.getId(); if (id!=null) reply.put(ID_FIELD,id); for (Extension e:_extensions) reply=e.sendMeta(reply); transport.send(reply); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ protected class UnsubscribeHandler extends Handler { @Override ChannelId getMetaChannelId() { return META_UNSUBSCRIBE_ID; } @Override public void handle(ClientImpl client, Transport transport, Message message) throws IOException { if (client==null) { unknownClient(transport,META_UNSUBSCRIBE); return; } String channel_id=(String)message.get(SUBSCRIPTION_FIELD); ChannelImpl channel=getChannel(channel_id); if (channel!=null) channel.unsubscribe(client); Message reply=newMessage(message); reply.put(CHANNEL_FIELD,META_UNSUBSCRIBE); if (channel!=null) { channel.unsubscribe(client); reply.put(SUBSCRIPTION_FIELD,channel.getId()); reply.put(SUCCESSFUL_FIELD,Boolean.TRUE); } else reply.put(SUCCESSFUL_FIELD,Boolean.FALSE); String id=message.getId(); if (id!=null) reply.put(ID_FIELD,id); for (Extension e:_extensions) reply=e.sendMeta(reply); transport.send(reply); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ protected class PingHandler extends Handler { @Override ChannelId getMetaChannelId() { return META_PING_ID; } @Override public void handle(ClientImpl client, Transport transport, Message message) throws IOException { Message reply=newMessage(message); reply.put(CHANNEL_FIELD,META_PING); reply.put(SUCCESSFUL_FIELD,Boolean.TRUE); String id=message.getId(); if (id!=null) reply.put(ID_FIELD,id); for (Extension e:_extensions) reply=e.sendMeta(reply); transport.send(reply); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ protected class ServiceChannel extends ChannelImpl { ServiceChannel(String id) { super(id,AbstractBayeux.this); } /* ------------------------------------------------------------ */ /* (non-Javadoc) * @see org.mortbay.cometd.ChannelImpl#addChild(org.mortbay.cometd.ChannelImpl) */ @Override public void addChild(ChannelImpl channel) { super.addChild(channel); setPersistent(true); } /* ------------------------------------------------------------ */ @Override public void subscribe(Client client) { if (client.isLocal()) super.subscribe(client); } } }