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