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}