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