org.cometd.server.BayeuxServerImpl Maven / Gradle / Ivy
/*
* Copyright (c) 2010 the original author or authors.
*
* 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.cometd.server;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicMarkableReference;
import org.cometd.bayeux.Channel;
import org.cometd.bayeux.ChannelId;
import org.cometd.bayeux.Message;
import org.cometd.bayeux.server.Authorizer;
import org.cometd.bayeux.server.BayeuxContext;
import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.bayeux.server.ConfigurableServerChannel.Initializer;
import org.cometd.bayeux.server.LocalSession;
import org.cometd.bayeux.server.SecurityPolicy;
import org.cometd.bayeux.server.ServerChannel;
import org.cometd.bayeux.server.ServerChannel.MessageListener;
import org.cometd.bayeux.server.ServerChannel.ServerChannelListener;
import org.cometd.bayeux.server.ServerMessage;
import org.cometd.bayeux.server.ServerMessage.Mutable;
import org.cometd.bayeux.server.ServerSession;
import org.cometd.bayeux.server.ServerTransport;
import org.cometd.common.JSONContext;
import org.cometd.server.transport.JSONPTransport;
import org.cometd.server.transport.JSONTransport;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.thread.Timeout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Options to configure the server are:
* tickIntervalMsThe time in milliseconds between ticks to check for timeouts etc
* sweepIntervalMsThe time in milliseconds between sweeps of channels to remove
* invalid subscribers and non-persistent channels
*
*/
public class BayeuxServerImpl extends AbstractLifeCycle implements BayeuxServer
{
public static final String LOG_LEVEL = "logLevel";
public static final int OFF_LOG_LEVEL = 0;
public static final int CONFIG_LOG_LEVEL = 1;
public static final int INFO_LOG_LEVEL = 2;
public static final int DEBUG_LOG_LEVEL = 3;
public static final String JSON_CONTEXT = "jsonContext";
private final Logger _logger = LoggerFactory.getLogger(getClass().getName() + "." + System.identityHashCode(this));
private final SecureRandom _random = new SecureRandom();
private final List _listeners = new CopyOnWriteArrayList();
private final List _extensions = new CopyOnWriteArrayList();
private final ConcurrentMap _sessions = new ConcurrentHashMap();
private final ConcurrentMap _channels = new ConcurrentHashMap();
private final ConcurrentMap _transports = new ConcurrentHashMap();
private final List _allowedTransports = new CopyOnWriteArrayList();
private final ThreadLocal _currentTransport = new ThreadLocal();
private final Map _options = new TreeMap();
private final Timeout _timeout = new Timeout();
private final Map _handshakeAdvice;
private SecurityPolicy _policy = new DefaultSecurityPolicy();
private int _logLevel = OFF_LOG_LEVEL;
private JSONContext.Server _jsonContext;
private Timer _timer;
public BayeuxServerImpl()
{
addTransport(new JSONTransport(this));
addTransport(new JSONPTransport(this));
_handshakeAdvice = new HashMap(2);
_handshakeAdvice.put(Message.RECONNECT_FIELD, Message.RECONNECT_HANDSHAKE_VALUE);
_handshakeAdvice.put(Message.INTERVAL_FIELD, 0L);
}
/**
* @return The Logger used by this BayeuxServer instance
*/
public Logger getLogger()
{
return _logger;
}
private void debug(String message, Object... args)
{
if (_logLevel >= DEBUG_LOG_LEVEL)
_logger.info(message, args);
else
_logger.debug(message, args);
}
int getLogLevel()
{
return _logLevel;
}
@Override
protected void doStart() throws Exception
{
super.doStart();
_logLevel = OFF_LOG_LEVEL;
Object logLevelValue = getOption(LOG_LEVEL);
if (logLevelValue != null)
_logLevel = Integer.parseInt(String.valueOf(logLevelValue));
if (_logLevel >= CONFIG_LOG_LEVEL)
{
for (Map.Entry entry : getOptions().entrySet())
_logger.info("{}={}", entry.getKey(), entry.getValue());
}
initializeMetaChannels();
initializeJSONContext();
initializeDefaultTransports();
List allowedTransportNames = getAllowedTransports();
if (allowedTransportNames.isEmpty())
throw new IllegalStateException("No allowed transport names are configured, there must be at least one");
for (String allowedTransportName : allowedTransportNames)
{
ServerTransport allowedTransport = getTransport(allowedTransportName);
if (allowedTransport instanceof AbstractServerTransport)
((AbstractServerTransport)allowedTransport).init();
}
_timer = new Timer("BayeuxServer@" + hashCode(), true);
long tick_interval = getOption("tickIntervalMs", 97);
if (tick_interval > 0)
{
_timer.schedule(new TimerTask()
{
@Override
public void run()
{
_timeout.tick(System.currentTimeMillis());
}
}, tick_interval, tick_interval);
}
long sweep_interval = getOption("sweepIntervalMs", 997);
if (sweep_interval > 0)
{
_timer.schedule(new TimerTask()
{
@Override
public void run()
{
sweep();
}
}, sweep_interval, sweep_interval);
}
}
@Override
protected void doStop() throws Exception
{
super.doStop();
for (ServerTransport transport : _transports.values())
if (transport instanceof AbstractServerTransport)
((AbstractServerTransport)transport).destroy();
_listeners.clear();
_extensions.clear();
_sessions.clear();
_channels.clear();
_transports.clear();
_allowedTransports.clear();
_options.clear();
_timer.cancel();
}
protected void initializeMetaChannels()
{
createChannelIfAbsent(Channel.META_HANDSHAKE).getReference().addListener(new HandshakeHandler());
createChannelIfAbsent(Channel.META_CONNECT).getReference().addListener(new ConnectHandler());
createChannelIfAbsent(Channel.META_SUBSCRIBE).getReference().addListener(new SubscribeHandler());
createChannelIfAbsent(Channel.META_UNSUBSCRIBE).getReference().addListener(new UnsubscribeHandler());
createChannelIfAbsent(Channel.META_DISCONNECT).getReference().addListener(new DisconnectHandler());
}
protected void initializeJSONContext() throws Exception
{
Object option = getOption(JSON_CONTEXT);
if (option == null)
{
_jsonContext = new JettyJSONContextServer();
}
else
{
if (option instanceof String)
{
Class> jsonContextClass = Thread.currentThread().getContextClassLoader().loadClass((String)option);
if (JSONContext.Server.class.isAssignableFrom(jsonContextClass))
{
_jsonContext = (JSONContext.Server)jsonContextClass.newInstance();
}
else
{
throw new IllegalArgumentException("Invalid " + JSONContext.Server.class.getName() + " implementation class");
}
}
else if (option instanceof JSONContext.Server)
{
_jsonContext = (JSONContext.Server)option;
}
else
{
throw new IllegalArgumentException("Invalid " + JSONContext.Server.class.getName() + " implementation class");
}
}
_options.put(JSON_CONTEXT, _jsonContext);
}
/** Initialize the default transports.
* This method creates a {@link JSONTransport} and a {@link JSONPTransport}.
* If no allowed transport have been set then adds all known transports as allowed transports.
*/
protected void initializeDefaultTransports()
{
if (_allowedTransports.size()==0)
{
for (ServerTransport t : _transports.values())
_allowedTransports.add(t.getName());
}
debug("Allowed Transports: {}", _allowedTransports);
}
public void startTimeout(Timeout.Task task, long interval)
{
_timeout.schedule(task, interval);
}
public void cancelTimeout(Timeout.Task task)
{
task.cancel();
}
public ChannelId newChannelId(String id)
{
ServerChannelImpl channel = _channels.get(id);
if (channel!=null)
return channel.getChannelId();
return new ChannelId(id);
}
public Map getOptions()
{
return _options;
}
/**
* @see org.cometd.bayeux.Bayeux#getOption(java.lang.String)
*/
public Object getOption(String qualifiedName)
{
return _options.get(qualifiedName);
}
/** Get an option value as a long
* @param name The option name
* @param dft The default value
* @return long value
*/
protected long getOption(String name, long dft)
{
Object val=getOption(name);
if (val==null)
return dft;
if (val instanceof Number)
return ((Number)val).longValue();
return Long.parseLong(val.toString());
}
/**
* @see org.cometd.bayeux.Bayeux#getOptionNames()
*/
public Set getOptionNames()
{
return _options.keySet();
}
/**
* @see org.cometd.bayeux.Bayeux#setOption(java.lang.String, java.lang.Object)
*/
public void setOption(String qualifiedName, Object value)
{
_options.put(qualifiedName, value);
}
public void setOptions(Map options)
{
_options.putAll(options);
}
public long randomLong()
{
return _random.nextLong();
}
public void setCurrentTransport(AbstractServerTransport transport)
{
_currentTransport.set(transport);
}
public ServerTransport getCurrentTransport()
{
return _currentTransport.get();
}
public BayeuxContext getContext()
{
ServerTransport transport=_currentTransport.get();
return transport==null?null:transport.getContext();
}
public SecurityPolicy getSecurityPolicy()
{
return _policy;
}
public boolean createIfAbsent(String channelName, Initializer... initializers)
{
return createChannelIfAbsent(channelName, initializers).isMarked();
}
private AtomicMarkableReference createChannelIfAbsent(String channelName, Initializer... initializers)
{
boolean initialized = false;
ServerChannelImpl channel = _channels.get(channelName);
if (channel == null)
{
ChannelId channelId = new ChannelId(channelName);
// Be sure the parent is there
ServerChannelImpl parentChannel = null;
if (channelId.depth() > 1)
{
String parentName = channelId.getParent();
// If the parent needs to be re-created, we are missing its initializers,
// but there is nothing we can do: in this case, the application needs
// to make the parent persistent through an initializer.
parentChannel = createChannelIfAbsent(parentName).getReference();
}
ServerChannelImpl candidate = new ServerChannelImpl(this, channelId, parentChannel);
channel = _channels.putIfAbsent(channelName, candidate);
if (channel == null)
{
// My candidate channel was added to the map, so I'd better initialize it
channel = candidate;
debug("Added channel {}", channel);
try
{
for (Initializer initializer : initializers)
notifyConfigureChannel(initializer, channel);
for (BayeuxServer.BayeuxServerListener listener : _listeners)
{
if (listener instanceof ServerChannel.Initializer)
notifyConfigureChannel((Initializer)listener, channel);
}
}
finally
{
channel.initialized();
}
for (BayeuxServer.BayeuxServerListener listener : _listeners)
{
if (listener instanceof BayeuxServer.ChannelListener)
notifyChannelAdded((ChannelListener)listener, channel);
}
initialized = true;
}
}
else
{
channel.resetSweeperPasses();
// Double check if the sweeper removed this channel between the check at the top and here.
// This is not 100% fool proof (e.g. this thread is preempted long enough for the sweeper
// to remove the channel, but the alternative is to have a global lock)
_channels.putIfAbsent(channelName, channel);
}
// Another thread may add this channel concurrently, so wait until it is initialized
channel.waitForInitialized();
return new AtomicMarkableReference(channel, initialized);
}
private void notifyConfigureChannel(Initializer listener, ServerChannel channel)
{
try
{
listener.configureChannel(channel);
}
catch (Exception x)
{
_logger.info("Exception while invoking listener " + listener, x);
}
}
private void notifyChannelAdded(ChannelListener listener, ServerChannel channel)
{
try
{
listener.channelAdded(channel);
}
catch (Exception x)
{
_logger.info("Exception while invoking listener " + listener, x);
}
}
public List getSessions()
{
return Collections.unmodifiableList(new ArrayList(_sessions.values()));
}
public ServerSession getSession(String clientId)
{
if (clientId==null)
return null;
return _sessions.get(clientId);
}
protected void addServerSession(ServerSessionImpl session)
{
_sessions.put(session.getId(),session);
for (BayeuxServerListener listener : _listeners)
{
if (listener instanceof BayeuxServer.SessionListener)
notifySessionAdded((SessionListener)listener, session);
}
}
private void notifySessionAdded(SessionListener listener, ServerSession session)
{
try
{
listener.sessionAdded(session);
}
catch (Exception x)
{
_logger.info("Exception while invoking listener " + listener, x);
}
}
/**
* @param session the session to remove
* @param timedout whether the remove reason is server-side expiration
* @return true if the session was removed and was connected
*/
public boolean removeServerSession(ServerSession session,boolean timedout)
{
debug("Removing session {}, timed out: {}", session, timedout);
ServerSessionImpl removed =_sessions.remove(session.getId());
if (removed == session)
{
// Invoke BayeuxServer.SessionListener first, so that the application
// can be "pre-notified" that a session is being removed before the
// application gets notifications of channel unsubscriptions
for (BayeuxServerListener listener : _listeners)
{
if (listener instanceof BayeuxServer.SessionListener)
notifySessionRemoved((SessionListener)listener, session, timedout);
}
return ((ServerSessionImpl)session).removed(timedout);
}
else
return false;
}
private void notifySessionRemoved(SessionListener listener, ServerSession session, boolean timedout)
{
try
{
listener.sessionRemoved(session, timedout);
}
catch (Exception x)
{
_logger.info("Exception while invoking listener " + listener, x);
}
}
protected ServerSessionImpl newServerSession()
{
return new ServerSessionImpl(this);
}
public LocalSession newLocalSession(String idHint)
{
return new LocalSessionImpl(this,idHint);
}
public ServerMessage.Mutable newMessage()
{
return new ServerMessageImpl();
}
public ServerMessage.Mutable newMessage(ServerMessage tocopy)
{
ServerMessage.Mutable mutable = newMessage();
for (String key : tocopy.keySet())
mutable.put(key,tocopy.get(key));
return mutable;
}
public void setSecurityPolicy(SecurityPolicy securityPolicy)
{
_policy=securityPolicy;
}
public void addExtension(Extension extension)
{
_extensions.add(extension);
}
public void removeExtension(Extension extension)
{
_extensions.remove(extension);
}
public List getExtensions()
{
return Collections.unmodifiableList(_extensions);
}
public void addListener(BayeuxServerListener listener)
{
if (listener == null)
throw new NullPointerException();
_listeners.add(listener);
}
public ServerChannel getChannel(String channelId)
{
ServerChannelImpl channel = _channels.get(channelId);
if (channel != null)
channel.waitForInitialized();
return channel;
}
public List getChannels()
{
List result = new ArrayList();
for (ServerChannelImpl channel : _channels.values())
{
channel.waitForInitialized();
result.add(channel);
}
return result;
}
/**
* Scans all the channels and returns those identified by a {@link ChannelId channel id}
* that is a child of the given {@code channelId}.
* @param channelId the parent channel id used to look for children
* @return a list of channels identified by a {@link ChannelId channel id}
* that is a child of the given {@code channelId}
* @deprecated because it scans all the existing channels and therefore is inefficient;
* if the functionality is needed, then we may consider adding it to ServerChannel.
*/
@Deprecated
public List getChannelChildren(ChannelId channelId)
{
ArrayList children = new ArrayList();
for (ServerChannelImpl channel :_channels.values())
{
channel.waitForInitialized();
if (channelId.isParentOf(channel.getChannelId()))
children.add(channel);
}
return children;
}
public void removeListener(BayeuxServerListener listener)
{
_listeners.remove(listener);
}
/** Extend and handle in incoming message.
* @param session The session if known
* @param message The message.
* @return An unextended reply message
*/
public ServerMessage.Mutable handle(ServerSessionImpl session, ServerMessage.Mutable message)
{
debug("> {} {}", message, session);
Mutable reply = createReply(message);
if (!extendRecv(session, message) || session != null && !session.extendRecv(message))
{
error(reply, "404::message deleted");
}
else
{
debug(">> {}", message);
String channelName = message.getChannel();
ServerChannel channel;
if (channelName == null)
{
error(reply, "400::channel missing");
}
else
{
channel = getChannel(channelName);
if (channel == null)
{
Authorizer.Result creationResult = isCreationAuthorized(session, message, channelName);
if (creationResult instanceof Authorizer.Result.Denied)
{
String denyReason = ((Authorizer.Result.Denied)creationResult).getReason();
error(reply, "403:" + denyReason + ":create denied");
}
else
{
channel = createChannelIfAbsent(channelName).getReference();
}
}
if (channel != null)
{
if (channel.isMeta())
{
if (session == null && !Channel.META_HANDSHAKE.equals(channelName))
{
unknownSession(reply);
}
else
{
doPublish(session, (ServerChannelImpl)channel, message);
}
}
else
{
if (session == null)
{
unknownSession(reply);
}
else
{
Authorizer.Result publishResult = isPublishAuthorized(channel, session, message);
if (publishResult instanceof Authorizer.Result.Denied)
{
String denyReason = ((Authorizer.Result.Denied)publishResult).getReason();
error(reply, "403:" + denyReason + ":publish denied");
}
else
{
channel.publish(session, message);
reply.setSuccessful(true);
}
}
}
}
}
}
// Here the reply may be null if this instance is stopped concurrently
debug("<< {}", reply);
return reply;
}
private Authorizer.Result isPublishAuthorized(ServerChannel channel, ServerSession session, ServerMessage message)
{
if (_policy != null && !_policy.canPublish(this, session, channel, message))
{
_logger.warn("{} denied Publish@{} by {}", new Object[]{session, channel.getId(), _policy});
return Authorizer.Result.deny("denied_by_security_policy");
}
return isOperationAuthorized(Authorizer.Operation.PUBLISH, session, message, channel.getChannelId());
}
private Authorizer.Result isSubscribeAuthorized(ServerChannel channel, ServerSession session, ServerMessage message)
{
if (_policy != null && !_policy.canSubscribe(this, session, channel, message))
{
_logger.warn("{} denied Subscribe@{} by {}", new Object[]{session, channel, _policy});
return Authorizer.Result.deny("denied_by_security_policy");
}
return isOperationAuthorized(Authorizer.Operation.SUBSCRIBE, session, message, channel.getChannelId());
}
private Authorizer.Result isCreationAuthorized(ServerSession session, ServerMessage message, String channel)
{
if (_policy != null && !_policy.canCreate(BayeuxServerImpl.this, session, channel, message))
{
_logger.warn("{} denied Create@{} by {}", new Object[]{session, message.getChannel(), _policy});
return Authorizer.Result.deny("denied_by_security_policy");
}
return isOperationAuthorized(Authorizer.Operation.CREATE, session, message, new ChannelId(channel));
}
private Authorizer.Result isOperationAuthorized(Authorizer.Operation operation, ServerSession session, ServerMessage message, ChannelId channelId)
{
List channels = new ArrayList();
for (String wildName : channelId.getWilds())
{
ServerChannelImpl channel = _channels.get(wildName);
if (channel != null)
channels.add(channel);
}
ServerChannelImpl candidate = _channels.get(channelId.toString());
if (candidate != null)
channels.add(candidate);
boolean called = false;
Authorizer.Result result = Authorizer.Result.ignore();
for (ServerChannel channel : channels)
{
for (Authorizer authorizer : channel.getAuthorizers())
{
called = true;
Authorizer.Result authorization = authorizer.authorize(operation, channelId, session, message);
debug("Authorizer {} on channel {} {} {} for channel {}", authorizer, channel, authorization, operation, channelId);
if (authorization instanceof Authorizer.Result.Denied)
{
result = authorization;
break;
}
else if (authorization instanceof Authorizer.Result.Granted)
{
result = authorization;
}
}
}
if (!called)
{
result = Authorizer.Result.grant();
debug("No authorizers, {} for channel {} {}", operation, channelId, result);
}
else
{
if (result instanceof Authorizer.Result.Ignored)
{
result = Authorizer.Result.deny("denied_by_not_granting");
debug("No authorizer granted {} for channel {}, authorization {}", operation, channelId, result);
}
else if (result instanceof Authorizer.Result.Granted)
{
debug("No authorizer denied {} for channel {}, authorization {}", operation, channelId, result);
}
}
// We need to make sure that this method returns a boolean result (granted or denied)
// but if it's denied, we need to return the object in order to access the deny reason
assert !(result instanceof Authorizer.Result.Ignored);
return result;
}
protected void doPublish(ServerSessionImpl from, ServerChannelImpl to, final ServerMessage.Mutable mutable)
{
// check the parent channels
String parent=to.getChannelId().getParent();
while (parent!=null)
{
ServerChannelImpl c = _channels.get(parent);
if (c==null)
return; // remove in progress
if (c.isLazy())
mutable.setLazy(true);
parent=c.getChannelId().getParent();
}
// Get the array of listening channels
final List wildIds=to.getChannelId().getWilds();
final ServerChannelImpl[] wild_channels = new ServerChannelImpl[wildIds.size()];
for (int i=wildIds.size();i-->0;)
wild_channels[i]=_channels.get(wildIds.get(i));
// Call the wild listeners
for (final ServerChannelImpl channel : wild_channels)
{
if (channel == null)
continue;
if (channel.isLazy())
mutable.setLazy(true);
for (ServerChannelListener listener : channel.getListeners())
if (listener instanceof MessageListener)
if (!notifyOnMessage((MessageListener)listener, from, to, mutable))
return;
}
// Call the leaf listeners
if (to.isLazy())
mutable.setLazy(true);
for (ServerChannelListener listener : to.getListeners())
if (listener instanceof MessageListener)
if (!notifyOnMessage((MessageListener)listener, from, to, mutable))
return;
// Exactly at this point, we convert the message to JSON and therefore
// any further modification will be lost.
// This is an optimization so that if the message is sent to a million
// subscribers, we generate the JSON only once.
// From now on, user code is passed a ServerMessage reference (and not
// ServerMessage.Mutable), and we attempt to return immutable data
// structures, even if it is not possible to guard against all cases.
// For example, it is impossible to prevent things like
// ((CustomObject)serverMessage.getData()).change() or
// ((Map)serverMessage.getExt().get("map")).put().
freeze(mutable);
// Call the wild subscribers, which can only get broadcast messages.
// We need a special treatment in case of subscription to /**, otherwise
// we will deliver meta messages and service messages as if it could be
// possible to subscribe to meta channels and service channels.
Set wildSubscribers = null;
if (ChannelId.isBroadcast(mutable.getChannel()))
{
for (final ServerChannelImpl channel : wild_channels)
{
if (channel == null)
continue;
for (ServerSession session : channel.getSubscribers())
{
if (wildSubscribers == null)
wildSubscribers = new HashSet();
if (wildSubscribers.add(session.getId()))
((ServerSessionImpl)session).doDeliver(from, mutable);
}
}
}
// Call the leaf subscribers
for (ServerSession session : to.getSubscribers())
{
if (wildSubscribers == null || !wildSubscribers.contains(session.getId()))
((ServerSessionImpl)session).doDeliver(from, mutable);
}
// Meta handlers
if (to.isMeta())
{
for (ServerChannelListener listener : to.getListeners())
if (listener instanceof BayeuxServerImpl.HandlerListener)
((BayeuxServerImpl.HandlerListener)listener).onMessage(from,mutable);
}
}
public void freeze(Mutable mutable)
{
ServerMessageImpl message = (ServerMessageImpl)mutable;
if (message.isFrozen())
return;
String json = _jsonContext.generate(message);
message.freeze(json);
}
private boolean notifyOnMessage(MessageListener listener, ServerSession from, ServerChannel to, Mutable mutable)
{
try
{
return listener.onMessage(from, to, mutable);
}
catch (Exception x)
{
_logger.info("Exception while invoking listener " + listener, x);
return true;
}
}
public ServerMessage.Mutable extendReply(ServerSessionImpl from, ServerSessionImpl to, ServerMessage.Mutable reply)
{
if (!extendSend(from, to, reply))
return null;
if (to != null)
{
if (reply.isMeta())
{
if(!to.extendSendMeta(reply))
return null;
}
else
{
ServerMessage newReply = to.extendSendMessage(reply);
if (newReply == null)
{
reply = null;
}
else if (newReply != reply)
{
if (newReply instanceof ServerMessage.Mutable)
reply = (ServerMessage.Mutable)newReply;
else
reply = newMessage(newReply);
}
}
}
return reply;
}
protected boolean extendRecv(ServerSession from, ServerMessage.Mutable message)
{
if (message.isMeta())
{
for (Extension extension : _extensions)
if (!notifyRcvMeta(extension, from, message))
return false;
}
else
{
for (Extension extension : _extensions)
if (!notifyRcv(extension, from, message))
return false;
}
return true;
}
private boolean notifyRcvMeta(Extension extension, ServerSession from, Mutable message)
{
try
{
return extension.rcvMeta(from, message);
}
catch (Exception x)
{
_logger.info("Exception while invoking extension " + extension, x);
return true;
}
}
private boolean notifyRcv(Extension extension, ServerSession from, Mutable message)
{
try
{
return extension.rcv(from, message);
}
catch (Exception x)
{
_logger.info("Exception while invoking extension " + extension, x);
return true;
}
}
protected boolean extendSend(ServerSession from, ServerSession to, Mutable message)
{
if (message.isMeta())
{
// Cannot use listIterator(int): it is not thread safe
ListIterator i = _extensions.listIterator();
while (i.hasNext())
i.next();
while (i.hasPrevious())
{
final Extension extension = i.previous();
if (!notifySendMeta(extension, to, message))
{
debug("Extension {} interrupted message processing for {}", extension, message);
return false;
}
}
}
else
{
ListIterator i = _extensions.listIterator();
while (i.hasNext())
i.next();
while (i.hasPrevious())
{
final Extension extension = i.previous();
if (!notifySend(extension, from, to, message))
{
debug("Extension {} interrupted message processing for {}", extension, message);
return false;
}
}
}
debug("< {}", message);
return true;
}
private boolean notifySendMeta(Extension extension, ServerSession to, Mutable message)
{
try
{
return extension.sendMeta(to, message);
}
catch (Exception x)
{
_logger.info("Exception while invoking extension " + extension, x);
return true;
}
}
private boolean notifySend(Extension extension, ServerSession from, ServerSession to, Mutable message)
{
try
{
return extension.send(from, to, message);
}
catch (Exception x)
{
_logger.info("Exception while invoking extension " + extension, x);
return true;
}
}
protected boolean removeServerChannel(ServerChannelImpl channel)
{
if(_channels.remove(channel.getId(),channel))
{
debug("Removed channel {}",channel);
for (BayeuxServerListener listener : _listeners)
{
if (listener instanceof BayeuxServer.ChannelListener)
notifyChannelRemoved((ChannelListener)listener, channel);
}
return true;
}
return false;
}
private void notifyChannelRemoved(ChannelListener listener, ServerChannelImpl channel)
{
try
{
listener.channelRemoved(channel.getId());
}
catch (Exception x)
{
_logger.info("Exception while invoking listener " + listener, x);
}
}
protected List getListeners()
{
return Collections.unmodifiableList(_listeners);
}
public Set getKnownTransportNames()
{
return _transports.keySet();
}
public ServerTransport getTransport(String transport)
{
return _transports.get(transport);
}
public void addTransport(ServerTransport transport)
{
debug("addTransport {} from {}",transport.getName(),transport.getClass());
_transports.put(transport.getName(), transport);
}
public void setTransports(ServerTransport... transports)
{
setTransports(Arrays.asList(transports));
}
public void setTransports(List transports)
{
_transports.clear();
for (ServerTransport transport : transports)
addTransport(transport);
}
public List getTransports()
{
return new ArrayList(_transports.values());
}
public List getAllowedTransports()
{
return Collections.unmodifiableList(_allowedTransports);
}
public void setAllowedTransports(String... allowed)
{
setAllowedTransports(Arrays.asList(allowed));
}
public void setAllowedTransports(List allowed)
{
debug("setAllowedTransport {} of {}",allowed,_transports);
_allowedTransports.clear();
for (String transport : allowed)
{
if (_transports.containsKey(transport))
_allowedTransports.add(transport);
}
debug("allowedTransports ", _allowedTransports);
}
protected void unknownSession(Mutable reply)
{
error(reply, "402::Unknown client");
if (Channel.META_HANDSHAKE.equals(reply.getChannel()) || Channel.META_CONNECT.equals(reply.getChannel()))
reply.put(Message.ADVICE_FIELD, _handshakeAdvice);
}
protected void error(ServerMessage.Mutable reply, String error)
{
reply.put(Message.ERROR_FIELD, error);
reply.setSuccessful(false);
}
protected ServerMessage.Mutable createReply(ServerMessage.Mutable message)
{
ServerMessage.Mutable reply=newMessage();
message.setAssociated(reply);
reply.setAssociated(message);
reply.setChannel(message.getChannel());
String id=message.getId();
if (id != null)
reply.setId(id);
return reply;
}
public void sweep()
{
for (ServerChannelImpl channel : _channels.values())
channel.sweep();
for (ServerTransport transport : _transports.values())
{
if (transport instanceof AbstractServerTransport)
((AbstractServerTransport)transport).sweep();
}
long now = System.currentTimeMillis();
for (ServerSessionImpl session : _sessions.values())
session.sweep(now);
}
public String dump()
{
StringBuilder b = new StringBuilder();
ArrayList