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 }