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     */
022    package org.granite.gravity.adapters;
023    
024    import java.util.concurrent.ConcurrentHashMap;
025    
026    import org.granite.gravity.AsyncPublishedMessage;
027    import org.granite.gravity.Channel;
028    import org.granite.gravity.MessagePublishingException;
029    import org.granite.logging.Logger;
030    import org.granite.messaging.service.ServiceException;
031    import org.granite.util.XMap;
032    
033    import flex.messaging.messages.AcknowledgeMessage;
034    import flex.messaging.messages.AsyncMessage;
035    import flex.messaging.messages.CommandMessage;
036    import flex.messaging.messages.ErrorMessage;
037    
038    /**
039     * @author William DRAI
040     */
041    public 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    }