001/*
002  GRANITE DATA SERVICES
003  Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004
005  This file is part of Granite Data Services.
006
007  Granite Data Services is free software; you can redistribute it and/or modify
008  it under the terms of the GNU Library General Public License as published by
009  the Free Software Foundation; either version 2 of the License, or (at your
010  option) any later version.
011
012  Granite Data Services is distributed in the hope that it will be useful, but
013  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015  for more details.
016
017  You should have received a copy of the GNU Library General Public License
018  along with this library; if not, see <http://www.gnu.org/licenses/>.
019*/
020
021package org.granite.gravity.gae;
022
023import java.io.Serializable;
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029
030import org.granite.gravity.AsyncHttpContext;
031import org.granite.gravity.AsyncPublishedMessage;
032import org.granite.gravity.Channel;
033import org.granite.gravity.MessagePublishingException;
034import org.granite.gravity.MessageReceivingException;
035import org.granite.gravity.Subscription;
036import org.granite.logging.Logger;
037
038import com.google.appengine.api.memcache.Expiration;
039import com.google.appengine.api.memcache.MemcacheService;
040import com.google.appengine.api.memcache.MemcacheServiceFactory;
041
042import flex.messaging.messages.AsyncMessage;
043import flex.messaging.messages.Message;
044
045/**
046 * @author William DRAI
047 */
048public class GAEChannel implements Channel, Serializable {
049
050        private static final long serialVersionUID = 5129029435795219401L;
051        
052        private static final Logger log = Logger.getLogger(GAEChannel.class);
053    
054    static final String MSG_COUNT_PREFIX = "org.granite.gravity.channel.msgCount.";
055    static final String MSG_PREFIX = "org.granite.gravity.channel.msg.";
056    
057    private static MemcacheService gaeCache = MemcacheServiceFactory.getMemcacheService();
058
059    protected final String id;
060    protected final String clientType;
061    protected final GAEGravity gravity;
062    protected final GAEChannelFactory factory;
063
064    private final Map<String, Subscription> subscriptions = new HashMap<String, Subscription>();
065    private final long expiration;
066
067
068    GAEChannel(GAEGravity gravity, String id, GAEChannelFactory factory, String clientType) {
069        if (id == null)
070                throw new NullPointerException("id cannot be null");
071        
072        this.id = id;
073        this.clientType = clientType;
074        this.factory = factory;
075        this.gravity = gravity;
076        this.expiration = gravity.getGravityConfig().getChannelIdleTimeoutMillis();
077    }
078
079        public String getId() {
080                return id;
081        }
082        
083        public String getClientType() {
084                return clientType;
085        }
086        
087        public GAEChannelFactory getFactory() {
088                return factory;
089        }
090        
091        public GAEGravity getGravity() {
092                return gravity;
093        }
094    
095    private Long msgCount() {
096        return (Long)gaeCache.get(MSG_COUNT_PREFIX + id);
097    }
098    
099    
100    public void close() {
101    }
102    
103        public void destroy() {
104        Long msgCount = msgCount();
105        if (msgCount != null) {
106                List<Object> list = new ArrayList<Object>();
107                list.add(MSG_COUNT_PREFIX + id);
108                for (long i = 0; i < msgCount; i++)
109                        list.add(MSG_PREFIX + id + "#" + i);
110                gaeCache.deleteAll(list);
111        }
112        this.subscriptions.clear();
113    }
114
115    
116        public void publish(AsyncPublishedMessage message) throws MessagePublishingException {
117                message.publish(this);
118    }
119
120        public void receive(AsyncMessage message) throws MessageReceivingException {
121        log.debug("Publish message to channel %s", id);
122//        System.err.println("Publish messages to channel " + id);
123        synchronized (this) {
124                Long msgCount = msgCount();
125                gaeCache.put(MSG_PREFIX + id + "#" + msgCount, message, Expiration.byDeltaMillis((int)expiration));
126                gaeCache.increment(MSG_COUNT_PREFIX + id, 1);
127        }
128        }
129    
130    public List<Message> takeMessages() {
131        log.debug("Try to take messages for channel %s", id);
132//        System.err.println("Try to take messages for channel " + id);
133        synchronized (this) {
134                Long msgCount = msgCount();
135                if (msgCount == null || msgCount == 0)
136                return null;
137
138            log.debug("Taking %s messages", msgCount);
139//            System.err.println("Taking " + msgCount + " messages");
140                List<Object> list = new ArrayList<Object>();
141                for (int i = 0; i < msgCount; i++)
142                        list.add(MSG_PREFIX + id + "#" + i);
143                Map<Object, Object> msgs = gaeCache.getAll(list);
144            List<Message> messages = new ArrayList<Message>();
145                for (int i = 0; i < msgCount; i++) {
146                        Message msg = (Message)msgs.get(list.get(i));
147                        if (msg != null)
148                                messages.add(msg);
149                }
150                
151                gaeCache.deleteAll(list);
152                gaeCache.put(MSG_COUNT_PREFIX + id, 0L, Expiration.byDeltaMillis((int)expiration));
153                
154            return messages.isEmpty() ? null : messages;
155        }
156    }
157
158
159    public Subscription addSubscription(String destination, String subTopicId, String subscriptionId, boolean noLocal) {
160        Subscription subscription = new Subscription(this, destination, subTopicId, subscriptionId, noLocal);
161        subscriptions.put(subscriptionId, subscription);
162        return subscription;
163    }
164
165    public Collection<Subscription> getSubscriptions() {
166        return subscriptions.values();
167    }
168    
169    public Subscription removeSubscription(String subscriptionId) {
170        return subscriptions.remove(subscriptionId);
171    }
172
173    
174    @Override
175    public boolean equals(Object obj) {
176        return (obj instanceof GAEChannel && id.equals(((GAEChannel)obj).id));
177    }
178
179    @Override
180    public int hashCode() {
181        return id.hashCode();
182    }
183
184        @Override
185    public String toString() {
186        return getClass().getName() + " {id=" + id + ", subscriptions=" + subscriptions + "}";
187    }
188
189
190        public boolean hasPublishedMessage() {
191                return false;
192        }
193
194        public boolean runPublish() {
195                return false;
196        }
197
198        public boolean hasReceivedMessage() {
199                return false;
200        }
201
202        public boolean runReceive() {
203                return false;
204        }
205
206        public boolean runReceived(AsyncHttpContext asyncHttpContext) {
207                return false;
208        }
209
210        public void run() {
211        }
212}