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.io.Serializable;
025    import java.util.ArrayList;
026    import java.util.Collection;
027    import java.util.HashMap;
028    import java.util.List;
029    import java.util.Map;
030    
031    import org.granite.gravity.AsyncHttpContext;
032    import org.granite.gravity.AsyncPublishedMessage;
033    import org.granite.gravity.Channel;
034    import org.granite.gravity.MessagePublishingException;
035    import org.granite.gravity.MessageReceivingException;
036    import org.granite.gravity.Subscription;
037    import org.granite.logging.Logger;
038    
039    import com.google.appengine.api.memcache.Expiration;
040    import com.google.appengine.api.memcache.MemcacheService;
041    import com.google.appengine.api.memcache.MemcacheServiceFactory;
042    
043    import flex.messaging.messages.AsyncMessage;
044    import flex.messaging.messages.Message;
045    
046    /**
047     * @author William DRAI
048     */
049    public 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    }