001package org.granite.gravity.glassfish; 002 003import java.io.ByteArrayInputStream; 004import java.io.ByteArrayOutputStream; 005import java.io.IOException; 006import java.io.ObjectInput; 007import java.io.ObjectOutput; 008import java.util.HashMap; 009import java.util.LinkedList; 010 011import org.granite.context.GraniteContext; 012import org.granite.context.SimpleGraniteContext; 013import org.granite.gravity.AbstractChannel; 014import org.granite.gravity.AsyncHttpContext; 015import org.granite.gravity.Gravity; 016import org.granite.gravity.GravityConfig; 017import org.granite.logging.Logger; 018 019import com.sun.grizzly.websockets.DataFrame; 020import com.sun.grizzly.websockets.WebSocket; 021import com.sun.grizzly.websockets.WebSocketListener; 022 023import flex.messaging.messages.AsyncMessage; 024import flex.messaging.messages.Message; 025 026 027public class GlassFishWebSocketChannel extends AbstractChannel implements WebSocketListener { 028 029 private static final Logger log = Logger.getLogger(GlassFishWebSocketChannel.class); 030 031 private WebSocket websocket; 032 private Message connectAckMessage; 033 034 035 public GlassFishWebSocketChannel(Gravity gravity, String id, GlassFishWebSocketChannelFactory factory, String clientType) { 036 super(gravity, id, factory, clientType); 037 } 038 039 public void setConnectAckMessage(Message ackMessage) { 040 this.connectAckMessage = ackMessage; 041 } 042 043 public void setWebSocket(WebSocket websocket) { 044 this.websocket = websocket; 045 this.websocket.add(this); 046 047 if (connectAckMessage == null) 048 return; 049 050 try { 051 // Return an acknowledge message with the server-generated clientId 052 byte[] resultData = serialize(getGravity(), new Message[] { connectAckMessage }); 053 websocket.send(resultData); 054 } 055 catch (IOException e) { 056 throw new RuntimeException("Could not send connect acknowledge", e); 057 } 058 059 connectAckMessage = null; 060 } 061 062 public void onConnect(WebSocket websocket) { 063 } 064 065 public void onClose(WebSocket websocket, DataFrame frame) { 066 } 067 068 public void onMessage(WebSocket websocket, byte[] data) { 069 try { 070 initializeRequest(getGravity()); 071 072 Message[] messages = deserialize(getGravity(), data); 073 074 log.debug(">> [AMF3 REQUESTS] %s", (Object)messages); 075 076 Message[] responses = null; 077 078 boolean accessed = false; 079 for (int i = 0; i < messages.length; i++) { 080 Message message = messages[i]; 081 082 // Ask gravity to create a specific response (will be null with a connect request from tunnel). 083 Message response = getGravity().handleMessage(getFactory(), message); 084 String channelId = (String)message.getClientId(); 085 086 // Mark current channel (if any) as accessed. 087 if (!accessed) 088 accessed = getGravity().access(channelId); 089 090 if (responses == null) 091 responses = new Message[messages.length]; 092 responses[i] = response; 093 } 094 095 log.debug("<< [AMF3 RESPONSES] %s", (Object)responses); 096 097 byte[] resultData = serialize(getGravity(), responses); 098 099 websocket.send(resultData); 100 } 101 catch (ClassNotFoundException e) { 102 log.error(e, "Could not handle incoming message data"); 103 } 104 catch (IOException e) { 105 log.error(e, "Could not handle incoming message data"); 106 } 107 finally { 108 cleanupRequest(); 109 } 110 } 111 112 private Gravity initializeRequest(Gravity gravity) { 113 SimpleGraniteContext.createThreadInstance(gravity.getGraniteConfig(), gravity.getServicesConfig(), sessionId, new HashMap<String, Object>(), clientType); 114 return gravity; 115 } 116 117 private static Message[] deserialize(Gravity gravity, byte[] data) throws ClassNotFoundException, IOException { 118 ByteArrayInputStream is = new ByteArrayInputStream(data); 119 try { 120 ObjectInput amf3Deserializer = gravity.getGraniteConfig().newAMF3Deserializer(is); 121 Object[] objects = (Object[])amf3Deserializer.readObject(); 122 Message[] messages = new Message[objects.length]; 123 System.arraycopy(objects, 0, messages, 0, objects.length); 124 125 return messages; 126 } 127 finally { 128 is.close(); 129 } 130 } 131 132 private static byte[] serialize(Gravity gravity, Message[] messages) throws IOException { 133 ByteArrayOutputStream os = null; 134 try { 135 os = new ByteArrayOutputStream(200*messages.length); 136 ObjectOutput amf3Serializer = gravity.getGraniteConfig().newAMF3Serializer(os); 137 amf3Serializer.writeObject(messages); 138 os.flush(); 139 return os.toByteArray(); 140 } 141 finally { 142 if (os != null) 143 os.close(); 144 } 145 } 146 147 private static void cleanupRequest() { 148 GraniteContext.release(); 149 } 150 151 @Override 152 public boolean runReceived(AsyncHttpContext asyncHttpContext) { 153 154 LinkedList<AsyncMessage> messages = null; 155 ByteArrayOutputStream os = null; 156 157 try { 158 receivedQueueLock.lock(); 159 try { 160 // Do we have any pending messages? 161 if (receivedQueue.isEmpty()) 162 return false; 163 164 // Both conditions are ok, get all pending messages. 165 messages = receivedQueue; 166 receivedQueue = new LinkedList<AsyncMessage>(); 167 } 168 finally { 169 receivedQueueLock.unlock(); 170 } 171 172 if (websocket == null || !websocket.isConnected()) 173 return false; 174 175 AsyncMessage[] messagesArray = new AsyncMessage[messages.size()]; 176 int i = 0; 177 for (AsyncMessage message : messages) 178 messagesArray[i++] = message; 179 180 // Setup serialization context (thread local) 181 Gravity gravity = getGravity(); 182 GraniteContext context = SimpleGraniteContext.createThreadInstance( 183 gravity.getGraniteConfig(), gravity.getServicesConfig(), sessionId, new HashMap<String, Object>(), clientType 184 ); 185 186 os = new ByteArrayOutputStream(500); 187 ObjectOutput amf3Serializer = context.getGraniteConfig().newAMF3Serializer(os); 188 189 log.debug("<< [MESSAGES for channel=%s] %s", this, messagesArray); 190 191 amf3Serializer.writeObject(messagesArray); 192 193 websocket.send(os.toByteArray()); 194 195 return true; // Messages were delivered, http context isn't valid anymore. 196 } 197 catch (IOException e) { 198 log.warn(e, "Could not send messages to channel: %s (retrying later)", this); 199 200 GravityConfig gravityConfig = getGravity().getGravityConfig(); 201 if (gravityConfig.isRetryOnError()) { 202 receivedQueueLock.lock(); 203 try { 204 if (receivedQueue.size() + messages.size() > gravityConfig.getMaxMessagesQueuedPerChannel()) { 205 log.warn( 206 "Channel %s has reached its maximum queue capacity %s (throwing %s messages)", 207 this, 208 gravityConfig.getMaxMessagesQueuedPerChannel(), 209 messages.size() 210 ); 211 } 212 else 213 receivedQueue.addAll(0, messages); 214 } 215 finally { 216 receivedQueueLock.unlock(); 217 } 218 } 219 220 return true; // Messages weren't delivered, but http context isn't valid anymore. 221 } 222 finally { 223 if (os != null) { 224 try { 225 os.close(); 226 } 227 catch (Exception e) { 228 // Could not close bytearray ??? 229 } 230 } 231 232 // Cleanup serialization context (thread local) 233 try { 234 GraniteContext.release(); 235 } 236 catch (Exception e) { 237 // should never happen... 238 } 239 } 240 } 241 242 @Override 243 public void destroy() { 244 try { 245 super.destroy(); 246 } 247 finally { 248 close(); 249 } 250 } 251 252 public void close() { 253 if (websocket != null) { 254 websocket.close(1000, "Channel closed"); 255 websocket = null; 256 } 257 } 258 259 @Override 260 protected boolean hasAsyncHttpContext() { 261 return true; 262 } 263 264 @Override 265 protected void releaseAsyncHttpContext(AsyncHttpContext context) { 266 } 267 268 @Override 269 protected AsyncHttpContext acquireAsyncHttpContext() { 270 return null; 271 } 272 273 public void onFragment(WebSocket arg0, String arg1, boolean arg2) { 274 } 275 276 public void onFragment(WebSocket arg0, byte[] arg1, boolean arg2) { 277 } 278 279 public void onMessage(WebSocket arg0, String arg1) { 280 } 281 282 public void onPing(WebSocket arg0, byte[] arg1) { 283 } 284 285 public void onPong(WebSocket arg0, byte[] arg1) { 286 } 287 288}