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;
16  import java.io.IOException;
17  import java.nio.ByteBuffer;
18  
19  import javax.servlet.ServletException;
20  import javax.servlet.http.HttpServletRequest;
21  import javax.servlet.http.HttpServletResponse;
22  
23  import org.cometd.Bayeux;
24  import org.cometd.Extension;
25  import org.cometd.Message;
26  import org.mortbay.util.ArrayQueue;
27  import org.mortbay.util.StringUtil;
28  
29  public class SuspendingCometdServlet extends AbstractCometdServlet
30  {
31      /* ------------------------------------------------------------ */
32      protected AbstractBayeux newBayeux()
33      {
34          return new SuspendingBayeux();
35      }
36  
37      /* ------------------------------------------------------------ */
38      public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
39      {
40          // Look for an existing client and protect from context restarts
41          SuspendingClient client=null;
42          Transport transport=null;
43          boolean connect=false;
44          int received=-1;
45          
46          // Have we seen this request before
47          boolean initial=false;
48          if (request.isInitial() || request.getAttribute(CLIENT_ATTR)==null)
49          {
50              initial=true;
51              
52              Message[] messages = getMessages(request);
53              received=messages.length;
54  
55              /* check jsonp parameter */
56              String jsonpParam=request.getParameter("jsonp");
57  
58              // Handle all messages
59              boolean response_pending=false;
60              try
61              {
62                  for (Message message : messages)
63                  {
64                      if (jsonpParam!=null)
65                          message.put("jsonp",jsonpParam);
66  
67                      if (client==null)
68                      {   
69                          client=(SuspendingClient)_bayeux.getClient((String)message.get(AbstractBayeux.CLIENT_FIELD));
70  
71                          // If no client,  SHOULD be a handshake, so force a transport and handle
72                          if (client==null)
73                          {
74                              // Setup a browser ID
75                              String browser_id=browserId(request);
76                              if (browser_id==null)
77                                  browser_id=newBrowserId(request,response);
78  
79                              if (transport==null)
80                              {
81                                  transport=_bayeux.newTransport(client,message);
82                                  transport.setResponse(response);
83                              }
84                              _bayeux.handle(null,transport,message);
85                              message=null;
86  
87                              continue;
88                          }
89                          else
90                          {
91                              String browser_id=browserId(request);
92                              if (browser_id!=null && (client.getBrowserId()==null || !client.getBrowserId().equals(browser_id)))
93                                  client.setBrowserId(browser_id);
94  
95                              // resolve transport
96                              if (transport==null)
97                              {
98                                  transport=_bayeux.newTransport(client,message);
99                                  transport.setResponse(response);
100                             }
101 
102                             // Tell client to hold messages as a response is likely to be sent.
103                             if (!transport.resumePoll())
104                             {
105                                 response_pending=true;
106                                 client.responsePending();
107                             }
108                         }
109                     }
110 
111                     String channel=_bayeux.handle(client,transport,message);
112                     connect|=AbstractBayeux.META_CONNECT.equals(channel);
113                 }
114             }
115             finally
116             {
117                 if (response_pending)
118                     client.responded();
119                 
120                 for (Message message : messages)
121                     ((MessageImpl)message).decRef();
122             }
123         }
124         else
125         {
126             transport=(Transport)request.getAttribute(TRANSPORT_ATTR);
127             transport.setResponse(response);
128             Object clientObj=request.getAttribute(CLIENT_ATTR); 
129             client=(clientObj instanceof ClientImpl)?(SuspendingClient)clientObj:null;
130             if (client!=null)
131                 client.setPollRequest(null);
132         }
133 
134         Message pollReply=null;
135         // Do we need to wait for messages
136         if (transport!=null)
137         {
138             pollReply=transport.getPollReply();
139             if (pollReply!=null)
140             {
141                 if (_bayeux.isLogDebug())
142                     _bayeux.logDebug("doPost: transport is polling");
143                 long timeout=client.getTimeout();
144                 if (timeout==0)
145                     timeout=_bayeux.getTimeout();
146 
147                 // Get messages or wait
148                 synchronized (client)
149                 {
150                     if (!client.hasMessages() && initial && received<=1)
151                     {
152                         // suspend and save state 
153                         request.suspend(timeout);
154                         client.setPollRequest(request);
155                         request.setAttribute(CLIENT_ATTR,client);
156                         request.setAttribute(TRANSPORT_ATTR,transport);
157                         return;
158                     }
159                     
160                     if (initial)
161                         client.access();
162                 }
163                 
164                 transport.setPollReply(null);
165 
166                 for (Extension e:_bayeux.getExtensions())
167                     pollReply=e.sendMeta(pollReply);     
168             }
169             else if (client!=null)
170             {
171                 client.access();
172             }
173         }
174 
175 
176         // Send any messages.
177         if (client!=null) 
178         {
179             synchronized(client)
180             {
181                 ArrayQueue<Message> messages= (ArrayQueue)client.getQueue();
182                 int size=messages.size();
183                 boolean flushed=false;
184 
185                 try
186                 {
187                     if (pollReply!=null)
188                     {
189                         // Can we bypass response generation?
190                         if (_refsThreshold>0 && size==1 && transport instanceof JSONTransport)
191                         {
192                             MessageImpl message = (MessageImpl)messages.peek();
193 
194                             // Is there a response already prepared?
195                             ByteBuffer buffer = message.getBuffer();
196                             if (buffer != null )
197                             {
198                                 request.setAttribute("org.mortbay.jetty.ResponseBuffer",buffer);
199                                 ((MessageImpl)message).decRef();
200                                 flushed=true;
201                             }
202                             else if (message.getRefs()>=_refsThreshold)
203                             {
204                                 // prepare a response buffer
205                                 byte[] contentBytes = ("[{\""+Bayeux.SUCCESSFUL_FIELD+"\":true,\""+
206                                         Bayeux.CHANNEL_FIELD+"\":\""+Bayeux.META_CONNECT+"\"},"+
207                                         message.getJSON()+"]").getBytes(StringUtil.__UTF8);
208                                 int contentLength = contentBytes.length;
209 
210                                 String headerString = "HTTP/1.1 200 OK\r\n"+
211                                 "Content-Type: text/json; charset=utf-8\r\n" +
212                                 "Content-Length: " + contentLength + "\r\n" +
213                                 "\r\n";
214 
215                                 byte[] headerBytes = headerString.getBytes(StringUtil.__UTF8);
216 
217                                 buffer = ByteBuffer.allocateDirect(headerBytes.length+contentLength);
218                                 buffer.put(headerBytes);
219                                 buffer.put(contentBytes);
220                                 buffer.flip();
221                                 message.setBuffer(buffer);
222                                 
223                                 request.setAttribute("org.mortbay.jetty.ResponseBuffer",buffer);
224                                 ((MessageImpl)message).decRef();
225                                 flushed=true;
226                             }
227                             else
228                                 transport.send(pollReply);            
229                         }
230                         else
231                             transport.send(pollReply);   
232                     }
233                     
234                     if (!flushed)
235                     {
236                         Message message = null;
237                         for (int i=0;i<size;i++)
238                         {
239                             message=messages.getUnsafe(i);
240                             transport.send(message);
241                         }
242                         
243                         transport.complete();
244                         flushed=true;                    
245                     }
246                 }
247                 finally
248                 {
249                     if (flushed)
250                         messages.clear();
251                 }
252             }
253             
254             if (transport.resumePoll())
255                 client.resume();
256         }
257         else if (transport!=null)
258         {
259             transport.complete();
260         }
261     }
262 }