001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 *   This file is part of the Granite Data Services Platform.
006 *
007 *   Granite Data Services is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *
012 *   Granite Data Services is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 *   General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public
018 *   License along with this library; if not, write to the Free Software
019 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 *   USA, or see <http://www.gnu.org/licenses/>.
021 */
022package org.granite.gravity.adapters;
023
024import java.util.concurrent.ConcurrentHashMap;
025import java.util.concurrent.ConcurrentMap;
026
027import org.granite.gravity.Channel;
028import org.granite.gravity.Subscription;
029
030import flex.messaging.messages.AsyncMessage;
031
032/**
033 * Adapted from Greg Wilkins code (Jetty).
034 * 
035 * @author William DRAI
036 */
037public class Topic {
038
039    private final TopicId id;
040    private final SimpleServiceAdapter serviceAdapter;
041
042    private ConcurrentMap<String, Subscription> subscriptions = new ConcurrentHashMap<String, Subscription>();
043    private ConcurrentMap<String, Topic> children = new ConcurrentHashMap<String, Topic>();
044    private Topic wild;
045    private Topic wildWild;
046
047
048    public Topic(String topicId, SimpleServiceAdapter serviceAdapter) {
049        this.id = new TopicId(topicId);
050        this.serviceAdapter = serviceAdapter;
051    }
052
053    public String getId() {
054        return id.toString();
055    }
056
057    public TopicId getTopicId() {
058        return id;
059    }
060
061    public Topic getChild(TopicId topicId) {
062        String next = topicId.getSegment(id.depth());
063        if (next == null)
064            return null;
065
066        Topic topic = children.get(next);
067
068        if (topic == null || topic.getTopicId().depth() == topicId.depth()) {
069            return topic;
070        }
071        return topic.getChild(topicId);
072    }
073
074    public void addChild(Topic topic) {
075        TopicId child = topic.getTopicId();
076        if (!id.isParentOf(child))
077            throw new IllegalArgumentException(id + " not parent of " + child);
078
079        String next = child.getSegment(id.depth());
080
081        if ((child.depth() - id.depth()) == 1) {
082            // add the topic to this topics
083            Topic old = children.putIfAbsent(next, topic);
084
085            if (old != null)
086                throw new IllegalArgumentException("Already Exists");
087
088            if (TopicId.WILD.equals(next))
089                wild = topic;
090            else if (TopicId.WILDWILD.equals(next))
091                wildWild = topic;
092        }
093        else {
094            Topic branch = serviceAdapter.getTopic((id.depth() == 0 ? "/" : (id.toString() + "/")) + next, true);
095            branch.addChild(topic);
096        }
097    }
098
099    public void subscribe(Channel channel, String destination, String subscriptionId, String selector, boolean noLocal) {
100        synchronized (this) {
101            Subscription subscription = channel.addSubscription(destination, getId(), subscriptionId, noLocal);
102            subscription.setSelector(selector);
103            subscriptions.putIfAbsent(subscriptionId, subscription);
104        }
105    }
106
107    public void unsubscribe(Channel channel, String subscriptionId) {
108        synchronized(this) {
109                subscriptions.remove(subscriptionId);
110            channel.removeSubscription(subscriptionId);
111        }
112    }
113
114
115    public void publish(TopicId to, Channel fromChannel, AsyncMessage msg) {
116        int tail = to.depth()-id.depth();
117
118        switch(tail) {
119            case 0:
120                for (Subscription subscription : subscriptions.values()) {
121                    AsyncMessage m = msg.clone();
122                    subscription.deliver(fromChannel, m);
123                }
124
125                break;
126
127            case 1:
128                if (wild != null) {
129                    for (Subscription subscription : wild.subscriptions.values()) {
130                        AsyncMessage m = msg.clone();
131                        subscription.deliver(fromChannel, m);
132                    }
133                }
134
135            default: {
136                if (wildWild != null) {
137                    for (Subscription subscription : wildWild.subscriptions.values()) {
138                        AsyncMessage m = msg.clone();
139                        subscription.deliver(fromChannel, m);
140                    }
141                }
142                String next = to.getSegment(id.depth());
143                Topic topic = children.get(next);
144                if (topic != null)
145                    topic.publish(to, fromChannel, msg);
146            }
147        }
148    }
149
150    @Override
151    public String toString() {
152        return id.toString() + " {" + children.values() + "}";
153    }
154}