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.io.Serializable;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031import org.granite.gravity.AsyncHttpContext;
032import org.granite.gravity.AsyncPublishedMessage;
033import org.granite.gravity.Channel;
034import org.granite.gravity.MessagePublishingException;
035import org.granite.gravity.MessageReceivingException;
036import org.granite.gravity.Subscription;
037import org.granite.logging.Logger;
038
039import com.google.appengine.api.memcache.Expiration;
040import com.google.appengine.api.memcache.MemcacheService;
041import com.google.appengine.api.memcache.MemcacheServiceFactory;
042
043import flex.messaging.messages.AsyncMessage;
044import flex.messaging.messages.Message;
045
046/**
047 * @author William DRAI
048 */
049public class GAEChannel implements Channel, Serializable {
050
051        private static final long serialVersionUID = 5129029435795219401L;
052        
053        private static final Logger log = Logger.getLogger(GAEChannel.class);
054    
055    static final String MSG_COUNT_PREFIX = "org.granite.gravity.channel.msgCount.";
056    static final String MSG_PREFIX = "org.granite.gravity.channel.msg.";
057    
058    private static MemcacheService gaeCache = MemcacheServiceFactory.getMemcacheService();
059
060    protected final String id;
061    protected final String clientType;
062    protected final GAEGravity gravity;
063    protected final GAEChannelFactory factory;
064
065    private final Map<String, Subscription> subscriptions = new HashMap<String, Subscription>();
066    private final long expiration;
067
068
069    GAEChannel(GAEGravity gravity, String id, GAEChannelFactory factory, String clientType) {
070        if (id == null)
071                throw new NullPointerException("id cannot be null");
072        
073        this.id = id;
074        this.clientType = clientType;
075        this.factory = factory;
076        this.gravity = gravity;
077        this.expiration = gravity.getGravityConfig().getChannelIdleTimeoutMillis();
078    }
079
080        public String getId() {
081                return id;
082        }
083        
084        public String getClientType() {
085                return clientType;
086        }
087        
088        public GAEChannelFactory getFactory() {
089                return factory;
090        }
091        
092        public GAEGravity getGravity() {
093                return gravity;
094        }
095    
096    private Long msgCount() {
097        return (Long)gaeCache.get(MSG_COUNT_PREFIX + id);
098    }
099    
100    
101    public void close() {
102    }
103    
104        public void destroy(boolean timeout) {
105        Long msgCount = msgCount();
106        if (msgCount != null) {
107                List<Object> list = new ArrayList<Object>();
108                list.add(MSG_COUNT_PREFIX + id);
109                for (long i = 0; i < msgCount; i++)
110                        list.add(MSG_PREFIX + id + "#" + i);
111                gaeCache.deleteAll(list);
112        }
113        this.subscriptions.clear();
114    }
115
116    
117        public void publish(AsyncPublishedMessage message) throws MessagePublishingException {
118                message.publish(this);
119    }
120
121        public void receive(AsyncMessage message) throws MessageReceivingException {
122        log.debug("Publish message to channel %s", id);
123//        System.err.println("Publish messages to channel " + id);
124        synchronized (this) {
125                Long msgCount = msgCount();
126                gaeCache.put(MSG_PREFIX + id + "#" + msgCount, message, Expiration.byDeltaMillis((int)expiration));
127                gaeCache.increment(MSG_COUNT_PREFIX + id, 1);
128        }
129        }
130    
131    public List<Message> takeMessages() {
132        log.debug("Try to take messages for channel %s", id);
133//        System.err.println("Try to take messages for channel " + id);
134        synchronized (this) {
135                Long msgCount = msgCount();
136                if (msgCount == null || msgCount == 0)
137                return null;
138
139            log.debug("Taking %s messages", msgCount);
140//            System.err.println("Taking " + msgCount + " messages");
141                List<Object> list = new ArrayList<Object>();
142                for (int i = 0; i < msgCount; i++)
143                        list.add(MSG_PREFIX + id + "#" + i);
144                Map<Object, Object> msgs = gaeCache.getAll(list);
145            List<Message> messages = new ArrayList<Message>();
146                for (int i = 0; i < msgCount; i++) {
147                        Message msg = (Message)msgs.get(list.get(i));
148                        if (msg != null)
149                                messages.add(msg);
150                }
151                
152                gaeCache.deleteAll(list);
153                gaeCache.put(MSG_COUNT_PREFIX + id, 0L, Expiration.byDeltaMillis((int)expiration));
154                
155            return messages.isEmpty() ? null : messages;
156        }
157    }
158
159
160    public Subscription addSubscription(String destination, String subTopicId, String subscriptionId, boolean noLocal) {
161        Subscription subscription = new Subscription(this, destination, subTopicId, subscriptionId, noLocal);
162        subscriptions.put(subscriptionId, subscription);
163        return subscription;
164    }
165
166    public Collection<Subscription> getSubscriptions() {
167        return subscriptions.values();
168    }
169    
170    public Subscription removeSubscription(String subscriptionId) {
171        return subscriptions.remove(subscriptionId);
172    }
173
174    
175    @Override
176    public boolean equals(Object obj) {
177        return (obj instanceof GAEChannel && id.equals(((GAEChannel)obj).id));
178    }
179
180    @Override
181    public int hashCode() {
182        return id.hashCode();
183    }
184
185        @Override
186    public String toString() {
187        return getClass().getName() + " {id=" + id + ", subscriptions=" + subscriptions + "}";
188    }
189
190
191        public boolean hasPublishedMessage() {
192                return false;
193        }
194
195        public boolean runPublish() {
196                return false;
197        }
198
199        public boolean hasReceivedMessage() {
200                return false;
201        }
202
203        public boolean runReceive() {
204                return false;
205        }
206
207        public boolean runReceived(AsyncHttpContext asyncHttpContext) {
208                return false;
209        }
210
211        public void run() {
212        }
213}