KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > directwebremoting > dwrp > PollHandler


1 /*
2  * Copyright 2005 Joe Walker
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  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.directwebremoting.dwrp;
17
18 import java.io.IOException JavaDoc;
19 import java.io.PrintWriter JavaDoc;
20 import java.util.Map JavaDoc;
21
22 import javax.servlet.http.HttpServletRequest JavaDoc;
23 import javax.servlet.http.HttpServletResponse JavaDoc;
24
25 import org.directwebremoting.WebContext;
26 import org.directwebremoting.WebContextFactory;
27 import org.directwebremoting.extend.ConverterManager;
28 import org.directwebremoting.extend.EnginePrivate;
29 import org.directwebremoting.extend.Handler;
30 import org.directwebremoting.extend.PageNormalizer;
31 import org.directwebremoting.extend.RealScriptSession;
32 import org.directwebremoting.extend.ScriptConduit;
33 import org.directwebremoting.extend.ScriptSessionManager;
34 import org.directwebremoting.extend.ServerLoadMonitor;
35 import org.directwebremoting.extend.WaitController;
36 import org.directwebremoting.util.Continuation;
37 import org.directwebremoting.util.Logger;
38 import org.directwebremoting.util.Messages;
39 import org.directwebremoting.util.MimeConstants;
40
41 /**
42  * A Marshaller that output plain Javascript.
43  * This marshaller can be tweaked to output Javascript in an HTML context.
44  * This class works in concert with CallScriptConduit, they should be
45  * considered closely related and it is important to understand what one does
46  * while editing the other.
47  * @author Joe Walker [joe at getahead dot ltd dot uk]
48  */

49 public class PollHandler implements Handler
50 {
51     /**
52      * @param plain Are we using plain javascript or html wrapped javascript
53      */

54     public PollHandler(boolean plain)
55     {
56         this.plain = plain;
57     }
58
59     /* (non-Javadoc)
60      * @see org.directwebremoting.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
61      */

62     public void handle(HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response) throws IOException JavaDoc
63     {
64         // We must parse the parameters before we setup the conduit because it's
65
// only after doing this that we know the scriptSessionId
66
WebContext webContext = WebContextFactory.get();
67
68         boolean isGet = request.getMethod().equals("GET");
69         Map JavaDoc parameters = (Map JavaDoc) request.getAttribute(ATTRIBUTE_PARAMETERS);
70         if (parameters == null)
71         {
72             try
73             {
74                 if (isGet)
75                 {
76                     parameters = ParseUtil.parseGet(request);
77                 }
78                 else
79                 {
80                     parameters = ParseUtil.parsePost(request);
81                 }
82                 request.setAttribute(ATTRIBUTE_PARAMETERS, parameters);
83             }
84             catch (Exception JavaDoc ex)
85             {
86                 // Send a batch exception to the server because the parse failed
87
String JavaDoc script = EnginePrivate.getRemoteHandleBatchExceptionScript(null, ex);
88                 sendErrorScript(response, script);
89                 return;
90             }
91         }
92
93         String JavaDoc batchId = extractParameter(request, parameters, ATTRIBUTE_CALL_ID, ProtocolConstants.INBOUND_KEY_BATCHID);
94         String JavaDoc scriptId = extractParameter(request, parameters, ATTRIBUTE_SESSION_ID, ProtocolConstants.INBOUND_KEY_SCRIPT_SESSIONID);
95         String JavaDoc page = extractParameter(request, parameters, ATTRIBUTE_PAGE, ProtocolConstants.INBOUND_KEY_PAGE);
96         String JavaDoc prString = extractParameter(request, parameters, ATTRIBUTE_PARTIAL_RESPONSE, ProtocolConstants.INBOUND_KEY_PARTIAL_RESPONSE);
97         int partialResponse = Integer.valueOf(prString).intValue();
98
99         // We might need to complain that reverse ajax is not enabled.
100
if (!activeReverseAjaxEnabled)
101         {
102             log.error("Polling and Comet are disabled. To enable them set the init-param activeReverseAjaxEnabled to true. See http://getahead.org/dwr/server/servlet for more.");
103             String JavaDoc script = EnginePrivate.getRemotePollCometDisabledScript(batchId);
104             sendErrorScript(response, script);
105             return;
106         }
107
108         if (!allowGetForSafariButMakeForgeryEasier && isGet)
109         {
110             // Send a batch exception to the server because the parse failed
111
String JavaDoc script = EnginePrivate.getRemoteHandleBatchExceptionScript(batchId, new SecurityException JavaDoc("GET Disallowed"));
112             sendErrorScript(response, script);
113             return;
114         }
115
116         // Various bits of parseResponse need to be stashed away places
117
String JavaDoc normalizedPage = pageNormalizer.normalizePage(page);
118
119         webContext.setCurrentPageInformation(normalizedPage, scriptId);
120         RealScriptSession scriptSession = (RealScriptSession) webContext.getScriptSession();
121
122         long maxConnectedTime = serverLoadMonitor.getConnectedTime();
123         long endTime = System.currentTimeMillis() + maxConnectedTime;
124
125         // If we are going to be doing any waiting then check for other threads
126
// from the same browser that are already waiting, and send them on
127
// their way
128
if (maxConnectedTime > 0)
129         {
130             notifyThreadsFromSameBrowser(request, scriptId);
131         }
132
133         ScriptConduit notifyConduit = new NotifyOnlyScriptConduit(scriptSession.getScriptLock());
134
135         // The pre-stream wait. A wait before we open any output stream
136
// Don't wait if we would wait for 0s or if there are queued scripts
137
boolean canWaitMore = true;
138         if (maxConnectedTime > 0 && !scriptSession.hasWaitingScripts())
139         {
140             canWaitMore = streamWait(request, notifyConduit, scriptSession, maxConnectedTime);
141         }
142
143         BaseScriptConduit conduit;
144         if (plain)
145         {
146             conduit = new PlainScriptConduit(response, batchId, converterManager);
147         }
148         else
149         {
150             //conduit = new Html4kScriptConduit(response, partialResponse, batchId, converterManager);
151
conduit = new HtmlScriptConduit(response, batchId, converterManager);
152         }
153
154         // How much longer do we wait now the stream is open?
155
long extraWait = endTime - System.currentTimeMillis();
156
157         // We might need to cut out waiting short to force proxies to flush
158
if (maxWaitAfterWrite != -1 && extraWait > maxWaitAfterWrite)
159         {
160             extraWait = maxWaitAfterWrite;
161         }
162
163         // Short-circut if we are not waiting at all
164
if (extraWait <= 0)
165         {
166             canWaitMore = false;
167         }
168
169         if (canWaitMore && partialResponse != PARTIAL_RESPONSE_NO)
170         {
171             streamWait(request, conduit, scriptSession, extraWait);
172         }
173         else
174         {
175             scriptSession.writeScripts(conduit);
176         }
177
178         int timeToNextPoll = serverLoadMonitor.getDisconnectedTime();
179
180         conduit.close(timeToNextPoll);
181     }
182
183     /**
184      * Perform a wait.
185      * @param request The HTTP request, needed to start a Jetty continuation
186      * @param conduit A conduit if there is an open stream or null if not
187      * @param scriptSession The script that we lock against
188      * @param wait How long do we wait for?
189      * @return True if the wait did not end in a shutdown request
190      * @throws IOException If an IO error occurs
191      */

192     protected boolean streamWait(HttpServletRequest JavaDoc request, ScriptConduit conduit, RealScriptSession scriptSession, long wait) throws IOException JavaDoc
193     {
194         Object JavaDoc lock = scriptSession.getScriptLock();
195         WaitController controller = new NotifyWaitController(lock);
196
197         try
198         {
199             serverLoadMonitor.threadWaitStarting(controller);
200             if (conduit != null)
201             {
202                 scriptSession.addScriptConduit(conduit);
203             }
204
205             synchronized (lock)
206             {
207                 // If this is Jetty then we can use Continuations
208
Continuation continuation = new Continuation(request);
209                 if (continuation.isAvailable())
210                 {
211                     if (!sleepWithContinuation(scriptSession, continuation, wait))
212                     {
213                         lock.wait(wait);
214                     }
215                 }
216                 else
217                 {
218                     lock.wait(wait);
219                 }
220
221                 if (conduit != null)
222                 {
223                     scriptSession.removeScriptConduit(conduit);
224                 }
225                 serverLoadMonitor.threadWaitEnding(controller);
226             }
227         }
228         catch (InterruptedException JavaDoc ex)
229         {
230             log.warn("Interupted", ex);
231
232             if (conduit != null)
233             {
234                 scriptSession.removeScriptConduit(conduit);
235             }
236             serverLoadMonitor.threadWaitEnding(controller);
237         }
238
239         return !controller.isShutdown();
240     }
241
242     /**
243      * Use a {@link ResumeContinuationScriptConduit} to wait
244      * @param scriptSession The session that we add the conduit to
245      * @param continuation The Jetty continuation object
246      * @param preStreamWaitTime The length of time to wait
247      * @return True if the continuation wait worked
248      */

249     protected boolean sleepWithContinuation(RealScriptSession scriptSession, Continuation continuation, long preStreamWaitTime)
250     {
251         ScriptConduit listener = null;
252
253         try
254         {
255             // create a listener
256
listener = (ScriptConduit) continuation.getObject();
257             if (listener == null)
258             {
259                 listener = new ResumeContinuationScriptConduit(continuation);
260                 continuation.setObject(listener);
261             }
262             scriptSession.addScriptConduit(listener);
263
264             // JETTY: throws a RuntimeException that must propogate to the container!
265
continuation.suspend(preStreamWaitTime);
266
267             scriptSession.removeScriptConduit(listener);
268         }
269         catch (Exception JavaDoc ex)
270         {
271             Continuation.rethrowIfContinuation(ex);
272
273             if (listener != null)
274             {
275                 scriptSession.removeScriptConduit(listener);
276             }
277
278             log.warn("Exception", ex);
279             return false;
280         }
281
282         return true;
283     }
284
285     /**
286      * Make other threads from the same browser stop waiting and continue
287      * @param request The HTTP request
288      * @param scriptId The session id of the current page
289      */

290     protected void notifyThreadsFromSameBrowser(HttpServletRequest JavaDoc request, String JavaDoc scriptId)
291     {
292         // First we check to see if there is already a connection from the
293
// current browser to this servlet
294
String JavaDoc otherScriptSessionId = (String JavaDoc) request.getSession().getAttribute(ATTRIBUTE_LONGPOLL_SESSION_ID);
295         if (otherScriptSessionId != null)
296         {
297             RealScriptSession previousSession = scriptSessionManager.getScriptSession(otherScriptSessionId);
298             Object JavaDoc lock = previousSession.getScriptLock();
299
300             // Unlock previous script session (request will be automatically finished)
301
synchronized (lock)
302             {
303                 lock.notifyAll();
304             }
305         }
306
307         request.getSession().setAttribute(ATTRIBUTE_LONGPOLL_SESSION_ID, scriptId);
308     }
309
310     /**
311      * Extract a parameter and ensure it is in the request.
312      * This is needed to cope with Jetty continuations that are not real
313      * continuations.
314      * @param request The HTTP request
315      * @param parameters The parameter list parsed out of the request
316      * @param attrName The name of the request attribute
317      * @param paramName The name of the parameter sent
318      * @return The found value
319      */

320     protected String JavaDoc extractParameter(HttpServletRequest JavaDoc request, Map JavaDoc parameters, String JavaDoc attrName, String JavaDoc paramName)
321     {
322         String JavaDoc id = (String JavaDoc) request.getAttribute(attrName);
323
324         if (id == null)
325         {
326             id = (String JavaDoc) parameters.remove(paramName);
327             request.setAttribute(attrName, id);
328         }
329
330         if (id == null)
331         {
332             throw new IllegalArgumentException JavaDoc(Messages.getString("PollHandler.MissingParameter", paramName));
333         }
334
335         return id;
336     }
337
338     /**
339      * Send a script to the browser and wrap it in the required prefixes etc.
340      * @param response The http response to write to
341      * @param script The script to write
342      * @throws IOException if writing fails.
343      */

344     protected void sendErrorScript(HttpServletResponse JavaDoc response, String JavaDoc script) throws IOException JavaDoc
345     {
346         PrintWriter JavaDoc out = response.getWriter();
347         if (plain)
348         {
349             response.setContentType(MimeConstants.MIME_PLAIN);
350         }
351         else
352         {
353             response.setContentType(MimeConstants.MIME_HTML);
354         }
355
356         out.println(ProtocolConstants.SCRIPT_START_MARKER);
357         out.println(script);
358         out.println(ProtocolConstants.SCRIPT_END_MARKER);
359     }
360
361     /**
362      * Accessor for the DefaultCreatorManager that we configure
363      * @param converterManager The new DefaultConverterManager
364      */

365     public void setConverterManager(ConverterManager converterManager)
366     {
367         this.converterManager = converterManager;
368     }
369
370     /**
371      * Accessor for the server load monitor
372      * @param serverLoadMonitor the new server load monitor
373      */

374     public void setServerLoadMonitor(ServerLoadMonitor serverLoadMonitor)
375     {
376         this.serverLoadMonitor = serverLoadMonitor;
377     }
378
379     /**
380      * Accessor for the PageNormalizer.
381      * @param pageNormalizer The new PageNormalizer
382      */

383     public void setPageNormalizer(PageNormalizer pageNormalizer)
384     {
385         this.pageNormalizer = pageNormalizer;
386     }
387
388     /**
389      * @param scriptSessionManager the scriptSessionManager to set
390      */

391     public void setScriptSessionManager(ScriptSessionManager scriptSessionManager)
392     {
393         this.scriptSessionManager = scriptSessionManager;
394     }
395
396     /**
397      * Use {@link #setActiveReverseAjaxEnabled(boolean)}
398      * @param pollAndCometEnabled Are we doing full reverse ajax
399      * @deprecated Use {@link #setActiveReverseAjaxEnabled(boolean)}
400      */

401     public void setPollAndCometEnabled(boolean pollAndCometEnabled)
402     {
403         this.activeReverseAjaxEnabled = pollAndCometEnabled;
404     }
405
406     /**
407      * Are we doing full reverse ajax
408      * @param activeReverseAjaxEnabled Are we doing full reverse ajax
409      */

410     public void setActiveReverseAjaxEnabled(boolean activeReverseAjaxEnabled)
411     {
412         this.activeReverseAjaxEnabled = activeReverseAjaxEnabled;
413     }
414
415     /**
416      * @param allowGetForSafariButMakeForgeryEasier Do we reduce security to help Safari
417      */

418     public void setAllowGetForSafariButMakeForgeryEasier(boolean allowGetForSafariButMakeForgeryEasier)
419     {
420         this.allowGetForSafariButMakeForgeryEasier = allowGetForSafariButMakeForgeryEasier;
421     }
422
423     /**
424      * Sometimes with proxies, you need to close the stream all the time to
425      * make the flush work. A value of -1 indicated that we do not do early
426      * closing after writes.
427      * @param maxWaitAfterWrite the maxWaitAfterWrite to set
428      */

429     public void setMaxWaitAfterWrite(int maxWaitAfterWrite)
430     {
431         this.maxWaitAfterWrite = maxWaitAfterWrite;
432     }
433
434     /**
435      * Are we doing full reverse ajax
436      */

437     protected boolean activeReverseAjaxEnabled = false;
438
439     /**
440      * By default we disable GET, but this hinders old Safaris
441      */

442     protected boolean allowGetForSafariButMakeForgeryEasier = false;
443
444     /**
445      * Sometimes with proxies, you need to close the stream all the time to
446      * make the flush work. A value of -1 indicated that we do not do early
447      * closing after writes.
448      */

449     protected int maxWaitAfterWrite = -1;
450
451     /**
452      * Are we using plain javascript or html wrapped javascript
453      */

454     protected boolean plain;
455
456     /**
457      * How we turn pages into the canonical form.
458      */

459     protected PageNormalizer pageNormalizer;
460
461     /**
462      * We need to tell the system that we are waiting so it can load adjust
463      */

464     protected ServerLoadMonitor serverLoadMonitor = null;
465
466     /**
467      * How we convert parameters
468      */

469     protected ConverterManager converterManager = null;
470
471     /**
472      * The owner of script sessions
473      */

474     protected ScriptSessionManager scriptSessionManager = null;
475
476     /**
477      * How we stash away the results of the request parse
478      */

479     public static final String JavaDoc ATTRIBUTE_PARAMETERS = "org.directwebremoting.dwrp.parameters";
480
481     /**
482      * How we stash away the results of the request parse
483      */

484     public static final String JavaDoc ATTRIBUTE_CALL_ID = "org.directwebremoting.dwrp.callId";
485
486     /**
487      * How we stash away the results of the request parse
488      */

489     public static final String JavaDoc ATTRIBUTE_SESSION_ID = "org.directwebremoting.dwrp.sessionId";
490
491     /**
492      * How we stash away the results of the request parse
493      */

494     public static final String JavaDoc ATTRIBUTE_PAGE = "org.directwebremoting.dwrp.page";
495
496     /**
497      * How we stash away the results of the request parse
498      */

499     public static final String JavaDoc ATTRIBUTE_PARTIAL_RESPONSE = "org.directwebremoting.dwrp.partialResponse";
500
501     /**
502      * We remember people that are in a long poll so we can kick them out
503      */

504     public static final String JavaDoc ATTRIBUTE_LONGPOLL_SESSION_ID = "org.directwebremoting.dwrp.longPollSessionId";
505
506     /**
507      * The client can not handle partial responses
508      */

509     protected static final int PARTIAL_RESPONSE_NO = 0;
510
511     /**
512      * The client can handle partial responses
513      */

514     protected static final int PARTIAL_RESPONSE_YES = 1;
515
516     /**
517      * The client can only handle partial responses with a 4k data post
518      * (can be whitespace) - we're talking IE here.
519      */

520     protected static final int PARTIAL_RESPONSE_FLUSH = 2;
521
522     /**
523      * The log stream
524      */

525     private static final Logger log = Logger.getLogger(PollHandler.class);
526 }
527
Popular Tags