// ======================================================================== // 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.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.cometd.Channel; import org.cometd.ChannelListener; import org.cometd.Client; import org.cometd.DataFilter; import org.cometd.Message; import org.cometd.SubscriptionListener; import org.mortbay.log.Log; import org.mortbay.util.LazyList; /* ------------------------------------------------------------ */ /** A Bayuex Channel * * @author gregw * */ public class ChannelImpl implements Channel { protected AbstractBayeux _bayeux; private ClientImpl[] _subscribers=new ClientImpl[0]; // copy on write private DataFilter[] _dataFilters=new DataFilter[0]; // copy on write private SubscriptionListener[] _subscriptionListeners=new SubscriptionListener[0]; // copy on write private ChannelId _id; private ConcurrentMap<String,ChannelImpl> _children = new ConcurrentHashMap<String, ChannelImpl>(); private ChannelImpl _wild; private ChannelImpl _wildWild; private boolean _persistent; private int _split; /* ------------------------------------------------------------ */ ChannelImpl(String id,AbstractBayeux bayeux) { _id=new ChannelId(id); _bayeux=bayeux; } /* ------------------------------------------------------------ */ public void addChild(ChannelImpl channel) { ChannelId child=channel.getChannelId(); if (!_id.isParentOf(child)) { throw new IllegalArgumentException(_id+" not parent of "+child); } String next = child.getSegment(_id.depth()); if ((child.depth()-_id.depth())==1) { // add the channel to this channels ChannelImpl old = _children.putIfAbsent(next,channel); if (old!=null) throw new IllegalArgumentException("Already Exists"); if (ChannelId.WILD.equals(next)) _wild=channel; else if (ChannelId.WILDWILD.equals(next)) _wildWild=channel; } else { ChannelImpl branch=_children.get(next); branch=(ChannelImpl)_bayeux.getChannel((_id.depth()==0?"/":(_id.toString()+"/"))+next,true); branch.addChild(channel); } _bayeux.addChannel(channel); } /* ------------------------------------------------------------ */ /** * @param filter */ public void addDataFilter(DataFilter filter) { synchronized(this) { _dataFilters=(DataFilter[])LazyList.addToArray(_dataFilters,filter,null); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /** * @return */ public ChannelId getChannelId() { return _id; } /* ------------------------------------------------------------ */ public ChannelImpl getChild(ChannelId id) { String next=id.getSegment(_id.depth()); if (next==null) return null; ChannelImpl channel = _children.get(next); if (channel==null || channel.getChannelId().depth()==id.depth()) { return channel; } return channel.getChild(id); } /* ------------------------------------------------------------ */ public void getChannels(List<Channel> list) { list.add(this); for (ChannelImpl channel: _children.values()) channel.getChannels(list); } /* ------------------------------------------------------------ */ public int getChannelCount() { int count = 1; for(ChannelImpl channel: _children.values()) count += channel.getChannelCount(); return count; } /* ------------------------------------------------------------ */ /** * @return */ public String getId() { return _id.toString(); } /* ------------------------------------------------------------ */ public boolean isPersistent() { return _persistent; } /* ------------------------------------------------------------ */ public void publish(Client fromClient, Object data, String msgId) { _bayeux.doPublish(getChannelId(),fromClient,data,msgId); } /* ------------------------------------------------------------ */ public boolean remove() { return _bayeux.removeChannel(this); } /* ------------------------------------------------------------ */ public boolean doRemove(ChannelImpl channel) { ChannelId channelId = channel.getChannelId(); String key = channelId.getSegment(channelId.depth()-1); if (_children.containsKey(key)) { ChannelImpl child = _children.get(key); synchronized (this) { synchronized (child) { if (!child.isPersistent() && child.getSubscriberCount()==0 && child.getChannelCount()==1) { _children.remove(key); return true; } else return false; } } } else { for (ChannelImpl child : _children.values()) { if (child.doRemove(channel)) return true; } } return false; } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /** * @param filter */ public DataFilter removeDataFilter(DataFilter filter) { synchronized(this) { _dataFilters=(DataFilter[])LazyList.removeFromArray(_dataFilters,filter); return filter; } } /* ------------------------------------------------------------ */ public void setPersistent(boolean persistent) { _persistent=persistent; } /* ------------------------------------------------------------ */ /** * @param client */ public void subscribe(Client client) { if (!(client instanceof ClientImpl)) throw new IllegalArgumentException("Client instance not obtained from Bayeux.newClient()"); synchronized (this) { for (ClientImpl c : _subscribers) { if (client.equals(c)) return; } _subscribers=(ClientImpl[])LazyList.addToArray(_subscribers,client,null); for (SubscriptionListener l : _subscriptionListeners) l.subscribed(client, this); } ((ClientImpl)client).addSubscription(this); } /* ------------------------------------------------------------ */ @Override public String toString() { return _id.toString(); } /* ------------------------------------------------------------ */ /** * @param client */ public void unsubscribe(Client client) { if (!(client instanceof ClientImpl)) throw new IllegalArgumentException("Client instance not obtained from Bayeux.newClient()"); ((ClientImpl)client).removeSubscription(this); synchronized(this) { _subscribers=(ClientImpl[])LazyList.removeFromArray(_subscribers,client); for (SubscriptionListener l : _subscriptionListeners) l.unsubscribed(client,this); if (!_persistent && _subscribers.length==0 && _children.size()==0) remove(); } } /* ------------------------------------------------------------ */ protected void doDelivery(ChannelId to, Client from, Message msg) { int tail = to.depth()-_id.depth(); Object data = msg.getData(); Object old = data; DataFilter[] filters=null; try { switch(tail) { case 0: { synchronized(this) { filters=_dataFilters; } for (DataFilter filter: filters) data=filter.filter(from,this,data); } break; case 1: if (_wild!=null) { synchronized(_wild) { filters=_wild._dataFilters; } for (DataFilter filter: filters) data=filter.filter(from,this,data); } default: if (_wildWild!=null) { synchronized(_wildWild) { filters=_wildWild._dataFilters; } for (DataFilter filter: filters) { data=filter.filter(from,this,data); } } } } catch (IllegalStateException e) { Log.debug(e); return; } if (data!=old) msg.put(AbstractBayeux.DATA_FIELD,data); ClientImpl[] subscribers; switch(tail) { case 0: synchronized (this) { subscribers=_subscribers; _split++; } if (subscribers.length>0) { // fair delivery int split=_split%_subscribers.length; for (int i=split;i<subscribers.length;i++) subscribers[i].doDelivery(from,msg); for (int i=0;i<split;i++) subscribers[i].doDelivery(from,msg); } break; case 1: if (_wild!=null) { synchronized (_wild) { subscribers=_wild._subscribers; } for (ClientImpl client: subscribers) { client.doDelivery(from,msg); } } default: { if (_wildWild!=null) { synchronized (_wildWild) { subscribers=_wildWild._subscribers; } for (ClientImpl client: subscribers) { client.doDelivery(from,msg); } } String next = to.getSegment(_id.depth()); ChannelImpl channel = _children.get(next); if (channel!=null) channel.doDelivery(to,from,msg); } } } /* ------------------------------------------------------------ */ public Collection<Client> getSubscribers() { synchronized(this) { return Arrays.asList((Client[])_subscribers); } } /* ------------------------------------------------------------ */ public int getSubscriberCount() { synchronized(this) { return _subscribers.length; } } /* ------------------------------------------------------------ */ /* (non-Javadoc) * @see dojox.cometd.Channel#getFilters() */ public Collection<DataFilter> getDataFilters() { synchronized(this) { return Arrays.asList(_dataFilters); } } /* ------------------------------------------------------------ */ public void addListener(ChannelListener listener) { synchronized(this) { if (listener instanceof SubscriptionListener) _subscriptionListeners=(SubscriptionListener[])LazyList.addToArray(_subscriptionListeners,listener,null); } } }