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 }