View Javadoc

1   //========================================================================
2   //Copyright 2007 Mort Bay Consulting Pty. Ltd.
3   //------------------------------------------------------------------------
4   //Licensed under the Apache License, Version 2.0 (the "License");
5   //you may not use this file except in compliance with the License.
6   //You may obtain a copy of the License at
7   //http://www.apache.org/licenses/LICENSE-2.0
8   //Unless required by applicable law or agreed to in writing, software
9   //distributed under the License is distributed on an "AS IS" BASIS,
10  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  //See the License for the specific language governing permissions and
12  //limitations under the License.
13  //========================================================================
14  
15  package org.mortbay.cometd.continuation;
16  
17  import java.io.IOException;
18  import java.nio.ByteBuffer;
19  
20  import javax.servlet.ServletException;
21  import javax.servlet.http.HttpServletRequest;
22  import javax.servlet.http.HttpServletResponse;
23  
24  import org.cometd.Bayeux;
25  import org.cometd.Client;
26  import org.cometd.Extension;
27  import org.cometd.Message;
28  import org.mortbay.cometd.AbstractBayeux;
29  import org.mortbay.cometd.AbstractCometdServlet;
30  import org.mortbay.cometd.ClientImpl;
31  import org.mortbay.cometd.JSONTransport;
32  import org.mortbay.cometd.MessageImpl;
33  import org.mortbay.cometd.Transport;
34  import org.mortbay.util.ArrayQueue;
35  import org.mortbay.util.StringUtil;
36  import org.mortbay.util.ajax.Continuation;
37  import org.mortbay.util.ajax.ContinuationSupport;
38  
39  
40  public class ContinuationCometdServlet extends AbstractCometdServlet
41  {
42      /* ------------------------------------------------------------ */
43      @Override
44      protected AbstractBayeux newBayeux()
45      {
46          return new ContinuationBayeux();
47      }
48  
49      /* ------------------------------------------------------------ */
50      @Override
51      protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
52      {
53          // Look for an existing client and protect from context restarts
54          Object clientObj=request.getAttribute(CLIENT_ATTR);
55          Transport transport=null;
56          int received=-1;
57          boolean metaConnectDeliveryOnly=false;
58          boolean pendingResponse=false;
59          boolean metaConnect=false;
60  
61          // Have we seen this request before
62          ContinuationClient client=(clientObj instanceof ClientImpl)?(ContinuationClient)clientObj:null;
63          if (client!=null)
64          {
65              // yes - extract saved properties
66              transport=(Transport)request.getAttribute(TRANSPORT_ATTR);
67              transport.setResponse(response);
68              metaConnectDeliveryOnly=client.isMetaConnectDeliveryOnly() || transport.isMetaConnectDeliveryOnly();
69              metaConnect=true;
70          }
71          else
72          {
73              Message[] messages = getMessages(request);
74              received=messages.length;
75  
76              /* check jsonp parameter */
77              String jsonpParam=request.getParameter("jsonp");
78  
79              // Handle all received messages
80              try
81              {
82                  for (Message message : messages)
83                  {
84                      if (jsonpParam!=null)
85                          message.put("jsonp",jsonpParam);
86  
87                      if (client==null)
88                      {
89                          client=(ContinuationClient)_bayeux.getClient((String)message.get(AbstractBayeux.CLIENT_FIELD));
90  
91                          // If no client,  SHOULD be a handshake, so force a transport and handle
92                          if (client==null)
93                          {
94                              // Setup a browser ID
95                              String browser_id=findBrowserId(request);
96                              if (browser_id==null)
97                                  browser_id=setBrowserId(request,response);
98  
99                              if (transport==null)
100                             {
101                                 transport=_bayeux.newTransport(client,message);
102                                 transport.setResponse(response);
103                                 metaConnectDeliveryOnly=transport.isMetaConnectDeliveryOnly();
104                             }
105                             _bayeux.handle(null,transport,message);
106                             message=null;
107                             continue;
108                         }
109                     }
110                     
111                     String browser_id=findBrowserId(request);
112                     if (browser_id!=null && (client.getBrowserId()==null || !client.getBrowserId().equals(browser_id)))
113                         client.setBrowserId(browser_id);
114 
115                     // resolve transport
116                     if (transport==null)
117                     {
118                         transport=_bayeux.newTransport(client,message);
119                         transport.setResponse(response);
120                         metaConnectDeliveryOnly=client.isMetaConnectDeliveryOnly() || transport.isMetaConnectDeliveryOnly();
121                     }
122 
123                     // Tell client to hold messages as a response is likely to be sent.
124                     if (!metaConnectDeliveryOnly && !pendingResponse)
125                     {
126                         pendingResponse=true;
127                         client.responsePending();
128                     }
129                     
130                     if (Bayeux.META_CONNECT.equals(message.getChannel()))
131                         metaConnect=true;
132                     
133                     _bayeux.handle(client,transport,message);
134                 }
135             }
136             finally
137             {
138                 for (Message message : messages)
139                     ((MessageImpl)message).decRef();
140                 if (pendingResponse)
141                 {
142                     client.responded();
143                 }
144             }
145         }
146 
147         Message metaConnectReply=null;
148         
149         // Do we need to wait for messages
150         if (transport!=null)
151         {
152             metaConnectReply=transport.getMetaConnectReply();
153             if (metaConnectReply!=null)
154             {
155                 long timeout=client.getTimeout();
156                 if (timeout==0)
157                     timeout=_bayeux.getTimeout();
158 
159                 Continuation continuation=ContinuationSupport.getContinuation(request,client);
160 
161                 // Get messages or wait
162                 synchronized (client)
163                 {
164                     if (!client.hasNonLazyMessages() && !continuation.isPending()&& received<=1)
165                     {
166                         // save state and suspend
167                         ((ContinuationClient)client).setContinuation(continuation);
168                         request.setAttribute(CLIENT_ATTR,client);
169                         request.setAttribute(TRANSPORT_ATTR,transport);
170                         continuation.suspend(timeout);
171                     }
172 
173                     if (!continuation.isPending())
174                         client.access();
175 
176                     continuation.reset();
177                 }
178 
179                 ((ContinuationClient)client).setContinuation(null);
180                 transport.setMetaConnnectReply(null);
181 
182             }
183             else if (client!=null)
184             {
185                 client.access();
186             }
187         }
188 
189         if (client!=null)
190         {
191             if (metaConnectDeliveryOnly && !metaConnect)
192             {
193                 // wake up any long poll
194                 client.resume();
195             }
196             else
197             {
198                 // Send any queued messages.
199                 synchronized(client)
200                 {
201                     client.doDeliverListeners();
202 
203                     final ArrayQueue<Message> messages= (ArrayQueue)client.getQueue();
204                     final int size=messages.size();           
205 
206                     try
207                     {
208                         for (int i=0;i<size;i++)
209                         {
210                             final Message message=messages.getUnsafe(i);
211                             final MessageImpl mesgImpl=(message instanceof MessageImpl)?(MessageImpl)message:null;
212 
213                             // Can we short cut the message?
214                             if (i==0 && size==1 && mesgImpl!=null && _refsThreshold>0 && metaConnectReply!=null && transport instanceof JSONTransport)
215                             {
216                                 // is there a response already prepared
217                                 ByteBuffer buffer = mesgImpl.getBuffer();
218                                 if (buffer!=null)
219                                 {
220                                     // Send pre-prepared buffer
221                                     request.setAttribute("org.mortbay.jetty.ResponseBuffer",buffer);
222                                     if (metaConnectReply instanceof MessageImpl)
223                                         ((MessageImpl)metaConnectReply).decRef();
224                                     metaConnectReply=null;
225                                     transport=null;
226                                     mesgImpl.decRef();
227                                     continue;
228                                 }
229                                 else if (mesgImpl.getRefs()>=_refsThreshold)
230                                 {
231                                     // create multi-use buffer
232                                     byte[] contentBytes = ("["+mesgImpl.getJSON()+",{\""+Bayeux.SUCCESSFUL_FIELD+"\":true,\""+
233                                             Bayeux.CHANNEL_FIELD+"\":\""+Bayeux.META_CONNECT+"\"}]")
234                                             .getBytes(StringUtil.__UTF8);
235                                     int contentLength = contentBytes.length;
236 
237                                     String headerString = "HTTP/1.1 200 OK\r\n"+
238                                     "Content-Type: text/json; charset=utf-8\r\n" +
239                                     "Content-Length: " + contentLength + "\r\n" +
240                                     "\r\n";
241 
242                                     byte[] headerBytes = headerString.getBytes(StringUtil.__UTF8);
243 
244                                     buffer = ByteBuffer.allocateDirect(headerBytes.length+contentLength);
245                                     buffer.put(headerBytes);
246                                     buffer.put(contentBytes);
247                                     buffer.flip();
248 
249                                     mesgImpl.setBuffer(buffer);
250                                     request.setAttribute("org.mortbay.jetty.ResponseBuffer",buffer);
251                                     metaConnectReply=null;
252                                     if (metaConnectReply instanceof MessageImpl)
253                                         ((MessageImpl)metaConnectReply).decRef();
254                                     transport=null;
255                                     mesgImpl.decRef();
256                                     continue;
257                                 }
258                             }
259 
260                             if (message!=null)
261                                 transport.send(message);
262                             if (mesgImpl!=null)
263                                 mesgImpl.decRef();
264                         }
265                     }
266                     finally
267                     {
268                         messages.clear();
269                     }
270                 }
271 
272                 if (metaConnectReply!=null)
273                 {
274                     metaConnectReply=_bayeux.extendSendMeta(client,metaConnectReply);
275                     transport.send(metaConnectReply);
276                     if (metaConnectReply instanceof MessageImpl)
277                         ((MessageImpl)metaConnectReply).decRef();
278                 }
279             }
280         }
281         
282         if (transport!=null)
283             transport.complete();
284     }
285 
286     public void destroy()
287     {
288         if (_bayeux!=null)
289             ((ContinuationBayeux)_bayeux).destroy();
290     }
291 }