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          ContinuationClient client=(clientObj instanceof ClientImpl)?(ContinuationClient)clientObj:null;
56          Transport transport=null;
57          boolean connect=false;
58          int received=-1;
59  
60          // Have we seen this request before
61          if (client!=null)
62          {
63              // yes - extract saved properties
64              transport=(Transport)request.getAttribute(TRANSPORT_ATTR);
65              transport.setResponse(response);
66          }
67          else
68          {
69              Message[] messages = getMessages(request);
70              received=messages.length;
71  
72              /* check jsonp parameter */
73              String jsonpParam=request.getParameter("jsonp");
74  
75              // Handle all messages
76              try
77              {
78                  for (Message message : messages)
79                  {
80                      if (jsonpParam!=null)
81                          message.put("jsonp",jsonpParam);
82  
83                      if (client==null)
84                      {
85                          client=(ContinuationClient)_bayeux.getClient((String)message.get(AbstractBayeux.CLIENT_FIELD));
86  
87                          // If no client,  SHOULD be a handshake, so force a transport and handle
88                          if (client==null)
89                          {
90                              // Setup a browser ID
91                              String browser_id=findBrowserId(request);
92                              if (browser_id==null)
93                                  browser_id=setBrowserId(request,response);
94  
95                              if (transport==null)
96                              {
97                                  transport=_bayeux.newTransport(client,message);
98                                  transport.setResponse(response);
99                              }
100                             _bayeux.handle(null,transport,message);
101                             message=null;
102 
103                             continue;
104                         }
105                         else
106                         {
107                             String browser_id=findBrowserId(request);
108                             if (browser_id!=null && (client.getBrowserId()==null || !client.getBrowserId().equals(browser_id)))
109                                 client.setBrowserId(browser_id);
110 
111                             // resolve transport
112                             if (transport==null)
113                             {
114                                 transport=_bayeux.newTransport(client,message);
115                                 transport.setResponse(response);
116                             }
117 
118                             // Tell client to hold messages as a response is likely to be sent.
119                             if (!transport.resumePoll())
120                                 client.responsePending();
121                         }
122                     }
123 
124                     String channel=_bayeux.handle(client,transport,message);
125                     connect|=AbstractBayeux.META_CONNECT.equals(channel);
126                 }
127             }
128             finally
129             {
130                 if (transport!=null && client!=null && !transport.resumePoll())
131                     client.responded();
132 
133                 for (Message message : messages)
134                     ((MessageImpl)message).decRef();
135             }
136         }
137 
138         Message pollReply=null;
139         // Do we need to wait for messages
140         if (transport!=null)
141         {
142             pollReply=transport.getPollReply();
143             if (pollReply!=null)
144             {
145                 if (_bayeux.isLogDebug())
146                     _bayeux.logDebug("doPost: transport is polling");
147                 long timeout=client.getTimeout();
148                 if (timeout==0)
149                     timeout=_bayeux.getTimeout();
150 
151                 Continuation continuation=ContinuationSupport.getContinuation(request,client);
152 
153                 // Get messages or wait
154                 synchronized (client)
155                 {
156                     if (!client.hasMessages() && !continuation.isPending()&& received<=1)
157                     {
158                         // save state and suspend
159                         ((ContinuationClient)client).setContinuation(continuation);
160                         request.setAttribute(CLIENT_ATTR,client);
161                         request.setAttribute(TRANSPORT_ATTR,transport);
162                         continuation.suspend(timeout);
163                     }
164 
165                     if (!continuation.isPending())
166                         client.access();
167 
168                     continuation.reset();
169                 }
170 
171                 ((ContinuationClient)client).setContinuation(null);
172                 transport.setPollReply(null);
173 
174                 pollReply=_bayeux.extendSendMeta(client,pollReply);
175             }
176             else if (client!=null)
177             {
178                 client.access();
179             }
180         }
181 
182         // Send any messages.
183         if (client!=null)
184         {
185             synchronized(client)
186             {
187                 client.doDeliverListeners();
188                 ArrayQueue<Message> messages= (ArrayQueue)client.getQueue();
189                 int size=messages.size();
190                 boolean flushed=false;
191 
192                 try
193                 {
194                     if (pollReply!=null)
195                     {
196                         // can we bypass response generation?
197                         if (_refsThreshold>0 && size==1 && transport instanceof JSONTransport)
198                         {
199                             MessageImpl message = (MessageImpl)messages.peek();
200 
201                             // is there a response already prepared
202                             ByteBuffer buffer = message.getBuffer();
203                             if (buffer!=null)
204                             {
205                                 request.setAttribute("org.mortbay.jetty.ResponseBuffer",buffer);
206                                 ((MessageImpl)message).decRef();
207                                 pollReply=null;
208                                 flushed=true;
209                             }
210                             else if (message.getRefs()>=_refsThreshold)
211                             {
212                                 byte[] contentBytes = ("["+message.getJSON()+",{\""+Bayeux.SUCCESSFUL_FIELD+"\":true,\""+
213                                         Bayeux.CHANNEL_FIELD+"\":\""+Bayeux.META_CONNECT+"\"}]")
214                                         .getBytes(StringUtil.__UTF8);
215                                 int contentLength = contentBytes.length;
216 
217                                 String headerString = "HTTP/1.1 200 OK\r\n"+
218                                 "Content-Type: text/json; charset=utf-8\r\n" +
219                                 "Content-Length: " + contentLength + "\r\n" +
220                                 "\r\n";
221 
222                                 byte[] headerBytes = headerString.getBytes(StringUtil.__UTF8);
223 
224                                 buffer = ByteBuffer.allocateDirect(headerBytes.length+contentLength);
225                                 buffer.put(headerBytes);
226                                 buffer.put(contentBytes);
227                                 buffer.flip();
228 
229                                 message.setBuffer(buffer);
230                                 request.setAttribute("org.mortbay.jetty.ResponseBuffer",buffer);
231                                 ((MessageImpl)message).decRef();
232                                 pollReply=null;
233                                 flushed=true;
234                             }
235                         }
236                     }
237 
238                     if (!flushed)
239                     {
240                         Message message = null;
241                         for (int i = 0;i<size;i++)
242                         {
243                             message=messages.getUnsafe(i);
244                             message=_bayeux.extendSendClient(null, client, message);
245                             if (message!=null)
246                                 transport.send(message);
247                         }
248 
249                         if (pollReply!=null)
250                             transport.send(pollReply);
251                         transport.complete();
252                         flushed=true;
253                     }
254                 }
255                 finally
256                 {
257                     if (flushed)
258                         messages.clear();
259                 }
260             }
261 
262             if (transport.resumePoll())
263                 client.resume();
264         }
265         else if (transport!=null)
266         {
267             transport.complete();
268         }
269     }
270 }