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}