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