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;
025
026import org.granite.gravity.AsyncPublishedMessage;
027import org.granite.gravity.Channel;
028import org.granite.gravity.MessagePublishingException;
029import org.granite.logging.Logger;
030import org.granite.messaging.service.ServiceException;
031import org.granite.util.XMap;
032
033import flex.messaging.messages.AcknowledgeMessage;
034import flex.messaging.messages.AsyncMessage;
035import flex.messaging.messages.CommandMessage;
036import flex.messaging.messages.ErrorMessage;
037
038/**
039 * @author William DRAI
040 */
041public class SimpleServiceAdapter extends ServiceAdapter {
042
043    private static final Logger log = Logger.getLogger(SimpleServiceAdapter.class);
044
045    private final Topic rootTopic = new Topic("/", this);
046    private transient ConcurrentHashMap<String, TopicId> _topicIdCache;
047    
048    private boolean noLocal = false;
049
050    @Override
051    public void configure(XMap adapterProperties, XMap destinationProperties) throws ServiceException {
052        super.configure(adapterProperties, destinationProperties);
053        
054        _topicIdCache = new ConcurrentHashMap<String, TopicId>();
055        
056        if (Boolean.TRUE.toString().equals(destinationProperties.get("no-local")))
057                noLocal = true;
058    }
059
060
061    public Topic getTopic(TopicId id) {
062        return rootTopic.getChild(id);
063    }
064
065    public Topic getTopic(String id) {
066        TopicId cid = getTopicId(id);
067        if (cid.depth() == 0)
068            return null;
069        return rootTopic.getChild(cid);
070    }
071
072    public Topic getTopic(String id, boolean create)  {
073        synchronized (this) {
074            Topic topic = getTopic(id);
075
076            if (topic == null && create) {
077                topic = new Topic(id, this);
078                rootTopic.addChild(topic);
079                log.debug("New Topic: %s", topic);
080            }
081            return topic;
082        }
083    }
084
085    public TopicId getTopicId(String id) {
086        TopicId tid = _topicIdCache.get(id);
087        if (tid == null) {
088            tid = new TopicId(id);
089            TopicId tmpTid = _topicIdCache.putIfAbsent(id, tid); 
090            if (tmpTid != null) 
091                tid = tmpTid; 
092        }
093        return tid;
094    }
095
096    public boolean hasTopic(String id) {
097        TopicId cid = getTopicId(id);
098        return rootTopic.getChild(cid) != null;
099    }
100
101    @Override
102    public Object invoke(Channel fromChannel, AsyncMessage message) {
103        String topicId = TopicId.normalize(((String)message.getHeader(AsyncMessage.SUBTOPIC_HEADER)));
104
105        AsyncMessage reply = null;
106
107        if (getSecurityPolicy().canPublish(fromChannel, topicId, message)) {
108            TopicId tid = getTopicId(topicId);
109
110                try {
111                                fromChannel.publish(new AsyncPublishedMessage(rootTopic, tid, message));
112                    reply = new AcknowledgeMessage(message);
113                    reply.setMessageId(message.getMessageId());
114                        }
115                catch (MessagePublishingException e) {
116                                log.error(e, "Error while publishing message: %s from channel %s to topic: %s", message, fromChannel, tid);
117                    reply = new ErrorMessage(message, null);
118                    ((ErrorMessage)reply).setFaultString("Server.Publish.Error");
119                        }
120        }
121        else {
122                log.warn("Channel %s tried to publish a message to topic %s", fromChannel, topicId);
123            reply = new ErrorMessage(message, null);
124            ((ErrorMessage)reply).setFaultString("Server.Publish.Denied");
125        }
126
127        return reply;
128    }
129
130    @Override
131    public Object manage(Channel fromChannel, CommandMessage message) {
132        AsyncMessage reply = null;
133
134        if (message.getOperation() == CommandMessage.SUBSCRIBE_OPERATION) {
135            String subscribeTopicId = TopicId.normalize(((String)message.getHeader(AsyncMessage.SUBTOPIC_HEADER)));
136
137            if (getSecurityPolicy().canSubscribe(fromChannel, subscribeTopicId, message)) {
138                Topic topic = getTopic(subscribeTopicId);
139                if (topic == null && getSecurityPolicy().canCreate(fromChannel, subscribeTopicId, message))
140                    topic = getTopic(subscribeTopicId, true);
141
142                if (topic != null) {
143                    String subscriptionId = (String)message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER);
144                    String selector = (String)message.getHeader(CommandMessage.SELECTOR_HEADER);
145                    if (subscriptionId == null)
146                        log.warn("No subscriptionId for subscription message");
147                    else
148                        topic.subscribe(fromChannel, message.getDestination(), subscriptionId, selector, noLocal);
149
150                    reply = new AcknowledgeMessage(message);
151                }
152                else {
153                    reply = new ErrorMessage(message, null);
154                    ((ErrorMessage)reply).setFaultString("Server.CreateTopic.Denied");
155                }
156            }
157            else {
158                reply = new ErrorMessage(message, null);
159                ((ErrorMessage)reply).setFaultString("Server.Subscribe.Denied");
160            }
161        }
162        else if (message.getOperation() == CommandMessage.UNSUBSCRIBE_OPERATION) {
163            String unsubscribeTopicId = TopicId.normalize(((String)message.getHeader(AsyncMessage.SUBTOPIC_HEADER)));
164
165            Topic topic = getTopic(unsubscribeTopicId);
166            String subscriptionId = null;
167            if (topic != null) {
168                subscriptionId = (String)message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER);
169                if (subscriptionId == null)
170                        log.warn("No subscriptionId for unsubscription message");
171                else
172                        topic.unsubscribe(fromChannel, subscriptionId);
173            }
174
175            reply = new AcknowledgeMessage(message);
176            reply.setHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER, subscriptionId);
177        }
178        else {
179            reply = new ErrorMessage(message, null);
180            ((ErrorMessage)reply).setFaultString("unknown operation");
181
182        }
183
184        return reply;
185    }
186}