KickJava   Java API By Example, From Geeks To Geeks.

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


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.lang.reflect.Method JavaDoc;
21 import java.util.ArrayList JavaDoc;
22 import java.util.Iterator JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.Map JavaDoc;
25
26 import javax.servlet.http.HttpServletRequest JavaDoc;
27 import javax.servlet.http.HttpServletResponse JavaDoc;
28
29 import org.directwebremoting.ScriptBuffer;
30 import org.directwebremoting.WebContext;
31 import org.directwebremoting.WebContextFactory;
32 import org.directwebremoting.extend.AccessControl;
33 import org.directwebremoting.extend.Call;
34 import org.directwebremoting.extend.Calls;
35 import org.directwebremoting.extend.ConverterManager;
36 import org.directwebremoting.extend.Creator;
37 import org.directwebremoting.extend.CreatorManager;
38 import org.directwebremoting.extend.InboundContext;
39 import org.directwebremoting.extend.InboundVariable;
40 import org.directwebremoting.extend.MarshallException;
41 import org.directwebremoting.extend.Marshaller;
42 import org.directwebremoting.extend.PageNormalizer;
43 import org.directwebremoting.extend.RealScriptSession;
44 import org.directwebremoting.extend.EnginePrivate;
45 import org.directwebremoting.extend.Replies;
46 import org.directwebremoting.extend.Reply;
47 import org.directwebremoting.extend.ScriptBufferUtil;
48 import org.directwebremoting.extend.ScriptConduit;
49 import org.directwebremoting.extend.ServerException;
50 import org.directwebremoting.extend.TypeHintContext;
51 import org.directwebremoting.util.DebuggingPrintWriter;
52 import org.directwebremoting.util.Logger;
53 import org.directwebremoting.util.Messages;
54
55 /**
56  * A Marshaller that output plain Javascript.
57  * This marshaller can be tweaked to output Javascript in an HTML context.
58  * This class works in concert with CallScriptConduit, they should be
59  * considered closely related and it is important to understand what one does
60  * while editing the other.
61  * @author Joe Walker [joe at getahead dot ltd dot uk]
62  */

63 public abstract class BaseCallMarshaller implements Marshaller
64 {
65     /* (non-Javadoc)
66      * @see org.directwebremoting.extend.Marshaller#marshallInbound(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
67      */

68     public Calls marshallInbound(HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response) throws IOException JavaDoc, ServerException
69     {
70         // We must parse the parameters before we setup the conduit because it's
71
// only after doing this that we know the scriptSessionId
72

73         WebContext webContext = WebContextFactory.get();
74         Batch batch = (Batch) request.getAttribute(ATTRIBUTE_BATCH);
75         if (batch == null)
76         {
77             batch = new Batch(request, crossDomainSessionSecurity, allowGetForSafariButMakeForgeryEasier, sessionCookieName);
78
79             // Save calls for retry exception
80
request.setAttribute(ATTRIBUTE_BATCH, batch);
81         }
82
83         // Various bits of the Batch need to be stashed away places
84
storeParsedRequest(request, webContext, batch);
85
86         Calls calls = batch.getCalls();
87
88         // Debug the environment
89
if (log.isDebugEnabled() && calls.getCallCount() > 0)
90         {
91             // We can just use 0 because they are all shared
92
InboundContext inctx = (InboundContext) batch.getInboundContexts().get(0);
93             StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
94
95             for (Iterator JavaDoc it = inctx.getInboundVariableNames(); it.hasNext();)
96             {
97                 String JavaDoc key = (String JavaDoc) it.next();
98                 InboundVariable value = inctx.getInboundVariable(key);
99                 if (key.startsWith(ProtocolConstants.INBOUND_CALLNUM_PREFIX) &&
100                     key.indexOf(ProtocolConstants.INBOUND_CALLNUM_SUFFIX + ProtocolConstants.INBOUND_KEY_ENV) != -1)
101                 {
102                     buffer.append(key);
103                     buffer.append('=');
104                     buffer.append(value.toString());
105                     buffer.append(", ");
106                 }
107             }
108
109             if (buffer.length() > 0)
110             {
111                 log.debug("Environment: " + buffer.toString());
112             }
113         }
114
115         callLoop:
116         for (int callNum = 0; callNum < calls.getCallCount(); callNum++)
117         {
118             Call call = calls.getCall(callNum);
119             InboundContext inctx = (InboundContext) batch.getInboundContexts().get(callNum);
120
121             // Get a list of the available matching methods with the coerced
122
// parameters that we will use to call it if we choose to use
123
// that method.
124
Creator creator = creatorManager.getCreator(call.getScriptName());
125
126             // Which method are we using?
127
Method JavaDoc method = findMethod(call, inctx);
128             if (method == null)
129             {
130                 String JavaDoc name = call.getScriptName() + '.' + call.getMethodName();
131                 String JavaDoc error = Messages.getString("BaseCallMarshaller.UnknownMethod", name);
132                 log.warn("Marshalling exception: " + error);
133
134                 call.setMethod(null);
135                 call.setParameters(null);
136                 call.setException(new IllegalArgumentException JavaDoc(error));
137
138                 continue callLoop;
139             }
140
141             call.setMethod(method);
142
143             // Check this method is accessible
144
accessControl.assertExecutionIsPossible(creator, call.getScriptName(), method);
145
146             // Convert all the parameters to the correct types
147
Object JavaDoc[] params = new Object JavaDoc[method.getParameterTypes().length];
148             for (int j = 0; j < method.getParameterTypes().length; j++)
149             {
150                 try
151                 {
152                     Class JavaDoc paramType = method.getParameterTypes()[j];
153                     InboundVariable param = inctx.getParameter(callNum, j);
154                     TypeHintContext incc = new TypeHintContext(converterManager, method, j);
155                     params[j] = converterManager.convertInbound(paramType, param, inctx, incc);
156                 }
157                 catch (MarshallException ex)
158                 {
159                     log.warn("Marshalling exception", ex);
160
161                     call.setMethod(null);
162                     call.setParameters(null);
163                     call.setException(ex);
164
165                     continue callLoop;
166                 }
167             }
168
169             call.setParameters(params);
170         }
171
172         return calls;
173     }
174
175     /**
176      * Build a Batch and put it in the request
177      * @param request Where we store the parsed data
178      * @param webContext We need to notify others of some of the data we find
179      * @param batch The parsed data to store
180      */

181     private void storeParsedRequest(HttpServletRequest JavaDoc request, WebContext webContext, Batch batch)
182     {
183         String JavaDoc normalizedPage = pageNormalizer.normalizePage(batch.getPage());
184         webContext.setCurrentPageInformation(normalizedPage, batch.getScriptSessionId());
185
186         // Remaining parameters get put into the request for later consumption
187
Map JavaDoc paramMap = batch.getSpareParameters();
188         if (paramMap.size() != 0)
189         {
190             for (Iterator JavaDoc it = paramMap.entrySet().iterator(); it.hasNext();)
191             {
192                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
193                 String JavaDoc key = (String JavaDoc) entry.getKey();
194                 String JavaDoc value = (String JavaDoc) entry.getValue();
195
196                 request.setAttribute(key, value);
197                 log.debug("Moved param to request: " + key + "=" + value);
198             }
199         }
200     }
201
202     /**
203      * Find the method the best matches the method name and parameters
204      * @param call The function call we are going to make
205      * @param inctx The data conversion context
206      * @return A matching method, or null if one was not found.
207      */

208     private Method JavaDoc findMethod(Call call, InboundContext inctx)
209     {
210         if (call.getScriptName() == null)
211         {
212             throw new IllegalArgumentException JavaDoc(Messages.getString("BaseCallMarshaller.MissingClassParam"));
213         }
214
215         if (call.getMethodName() == null)
216         {
217             throw new IllegalArgumentException JavaDoc(Messages.getString("BaseCallMarshaller.MissingMethodParam"));
218         }
219
220         Creator creator = creatorManager.getCreator(call.getScriptName());
221         Method JavaDoc[] methods = creator.getType().getMethods();
222         List JavaDoc available = new ArrayList JavaDoc();
223
224         methods:
225         for (int i = 0; i < methods.length; i++)
226         {
227             // Check method name and access
228
if (methods[i].getName().equals(call.getMethodName()))
229             {
230                 // Check number of parameters
231
if (methods[i].getParameterTypes().length == inctx.getParameterCount())
232                 {
233                     // Clear the previous conversion attempts (the param types
234
// will probably be different)
235
inctx.clearConverted();
236
237                     // Check parameter types
238
for (int j = 0; j < methods[i].getParameterTypes().length; j++)
239                     {
240                         Class JavaDoc paramType = methods[i].getParameterTypes()[j];
241                         if (!converterManager.isConvertable(paramType))
242                         {
243                             // Give up with this method and try the next
244
continue methods;
245                         }
246                     }
247
248                     available.add(methods[i]);
249                 }
250             }
251         }
252
253         // Pick a method to call
254
if (available.size() > 1)
255         {
256             log.warn("Warning multiple matching methods. Using first match.");
257         }
258
259         if (available.isEmpty())
260         {
261             return null;
262         }
263
264         // At the moment we are just going to take the first match, for a
265
// later increment we might pick the best implementation
266
return (Method JavaDoc) available.get(0);
267     }
268
269     /* (non-Javadoc)
270      * @see org.directwebremoting.Marshaller#marshallOutbound(org.directwebremoting.Replies, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
271      */

272     public void marshallOutbound(Replies replies, HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response) throws IOException JavaDoc
273     {
274         // Get the output stream and setup the mimetype
275
response.setContentType(getOutboundMimeType());
276         PrintWriter JavaDoc out;
277         if (log.isDebugEnabled())
278         {
279             // This might be considered evil - altering the program flow
280
// depending on the log status, however DebuggingPrintWriter is
281
// very thin and only about logging
282
out = new DebuggingPrintWriter("", response.getWriter());
283         }
284         else
285         {
286             out = response.getWriter();
287         }
288
289         // The conduit to pass on reverse ajax scripts
290
ScriptConduit conduit = new CallScriptConduit(out);
291
292         // Setup a debugging prefix
293
if (out instanceof DebuggingPrintWriter)
294         {
295             DebuggingPrintWriter dpw = (DebuggingPrintWriter) out;
296             dpw.setPrefix("out(" + conduit.hashCode() + "): ");
297         }
298
299         // Send the script prefix (if any)
300
sendOutboundScriptPrefix(out, replies.getBatchId());
301
302         // From the call to addScriptConduit() there could be 2 threads writing
303
// to 'out' so we synchronize on 'out' to make sure there are no
304
// clashes
305
RealScriptSession scriptSession = (RealScriptSession) WebContextFactory.get().getScriptSession();
306
307         out.println(ProtocolConstants.SCRIPT_CALL_INSERT);
308         scriptSession.writeScripts(conduit);
309         out.println(ProtocolConstants.SCRIPT_CALL_REPLY);
310
311         String JavaDoc batchId = replies.getBatchId();
312         for (int i = 0; i < replies.getReplyCount(); i++)
313         {
314             Reply reply = replies.getReply(i);
315             String JavaDoc callId = reply.getCallId();
316
317             try
318             {
319                 // The existance of a throwable indicates that something went wrong
320
if (reply.getThrowable() != null)
321                 {
322                     Throwable JavaDoc ex = reply.getThrowable();
323                     EnginePrivate.remoteHandleException(conduit, batchId, callId, ex);
324
325                     log.warn("--Erroring: batchId[" + batchId + "] message[" + ex.toString() + ']');
326                 }
327                 else
328                 {
329                     Object JavaDoc data = reply.getReply();
330                     EnginePrivate.remoteHandleCallback(conduit, batchId, callId, data);
331                 }
332             }
333             catch (IOException JavaDoc ex)
334             {
335                 // We're a bit stuck we died half way through writing so
336
// we can't be sure the browser can react to the failure.
337
// Since we can no longer do output we just log and end
338
log.error("--Output Error: batchId[" + batchId + "] message[" + ex.toString() + ']', ex);
339             }
340             catch (MarshallException ex)
341             {
342                 EnginePrivate.remoteHandleMarshallException(conduit, batchId, callId, ex);
343                 log.warn("--MarshallException: batchId=" + batchId + " class=" + ex.getConversionType().getName(), ex);
344             }
345             catch (Exception JavaDoc ex)
346             {
347                 // This is a bit of a "this can't happen" case so I am a bit
348
// nervous about sending the exception to the client, but we
349
// want to avoid silently dying so we need to do something.
350
EnginePrivate.remoteHandleException(conduit, batchId, callId, ex);
351                 log.error("--MarshallException: batchId=" + batchId + " message=" + ex.toString());
352             }
353         }
354
355         sendOutboundScriptSuffix(out, replies.getBatchId());
356     }
357
358     /* (non-Javadoc)
359      * @see org.directwebremoting.extend.Marshaller#marshallException(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Exception)
360      */

361     public void marshallException(HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response, Exception JavaDoc ex) throws IOException JavaDoc
362     {
363         response.setContentType(getOutboundMimeType());
364         PrintWriter JavaDoc out = response.getWriter();
365         Batch batch = (Batch) request.getAttribute(ATTRIBUTE_BATCH);
366
367         String JavaDoc batchId;
368         if (batch != null && batch.getCalls() != null)
369         {
370             batchId = batch.getCalls().getBatchId();
371         }
372         else
373         {
374             batchId = null;
375         }
376
377         sendOutboundScriptPrefix(out, batchId);
378         String JavaDoc script = EnginePrivate.getRemoteHandleBatchExceptionScript(batchId, ex);
379         out.print(script);
380         sendOutboundScriptSuffix(out, batchId);
381     }
382
383     /**
384      * Send a script to the browser
385      * @param out The stream to write to
386      * @param script The script to send
387      * @throws IOException If the write fails
388      */

389     protected abstract void sendScript(PrintWriter JavaDoc out, String JavaDoc script) throws IOException JavaDoc;
390
391     /**
392      * What mime type should we send to the browser for this data?
393      * @return A mime-type
394      */

395     protected abstract String JavaDoc getOutboundMimeType();
396
397     /**
398      * iframe mode starts as HTML, so get into script mode
399      * @param out The stream to write to
400      * @param batchId The batch identifier so we can prepare the environment
401      * @throws IOException If the write fails
402      */

403     protected abstract void sendOutboundScriptPrefix(PrintWriter JavaDoc out, String JavaDoc batchId) throws IOException JavaDoc;
404
405     /**
406      * iframe mode needs to get out of script mode
407      * @param out The stream to write to
408      * @param batchId The batch identifier so we can prepare the environment
409      * @throws IOException If the write fails
410      */

411     protected abstract void sendOutboundScriptSuffix(PrintWriter JavaDoc out, String JavaDoc batchId) throws IOException JavaDoc;
412
413     /* (non-Javadoc)
414      * @see org.directwebremoting.Marshaller#isConvertable(java.lang.Class)
415      */

416     public boolean isConvertable(Class JavaDoc paramType)
417     {
418         return converterManager.isConvertable(paramType);
419     }
420
421     /**
422      * Accessor for the DefaultCreatorManager that we configure
423      * @param converterManager The new DefaultConverterManager
424      */

425     public void setConverterManager(ConverterManager converterManager)
426     {
427         this.converterManager = converterManager;
428     }
429
430     /**
431      * Accessor for the DefaultCreatorManager that we configure
432      * @param creatorManager The new DefaultConverterManager
433      */

434     public void setCreatorManager(CreatorManager creatorManager)
435     {
436         this.creatorManager = creatorManager;
437     }
438
439     /**
440      * Accessor for the security manager
441      * @param accessControl The accessControl to set.
442      */

443     public void setAccessControl(AccessControl accessControl)
444     {
445         this.accessControl = accessControl;
446     }
447
448     /**
449      * Accessor for the PageNormalizer.
450      * @param pageNormalizer The new PageNormalizer
451      */

452     public void setPageNormalizer(PageNormalizer pageNormalizer)
453     {
454         this.pageNormalizer = pageNormalizer;
455     }
456
457     /**
458      * To we perform cross-domain session security checks?
459      * @param crossDomainSessionSecurity the cross domain session security setting
460      */

461     public void setCrossDomainSessionSecurity(boolean crossDomainSessionSecurity)
462     {
463         this.crossDomainSessionSecurity = crossDomainSessionSecurity;
464     }
465
466     /**
467      * @param allowGetForSafariButMakeForgeryEasier Do we reduce security to help Safari
468      */

469     public void setAllowGetForSafariButMakeForgeryEasier(boolean allowGetForSafariButMakeForgeryEasier)
470     {
471         this.allowGetForSafariButMakeForgeryEasier = allowGetForSafariButMakeForgeryEasier;
472     }
473
474     /**
475      * Alter the session cookie name from the default JSESSIONID.
476      * @param sessionCookieName the sessionCookieName to set
477      */

478     public void setSessionCookieName(String JavaDoc sessionCookieName)
479     {
480         this.sessionCookieName = sessionCookieName;
481     }
482
483     /**
484      * A ScriptConduit that works with the parent Marshaller.
485      * In some ways this is nasty because it has access to essentially private parts
486      * of BaseCallMarshaller, however there is nowhere sensible to store them
487      * within that class, so this is a hacky simplification.
488      * @author Joe Walker [joe at getahead dot ltd dot uk]
489      */

490     protected class CallScriptConduit extends ScriptConduit
491     {
492         /**
493          * Simple ctor
494          * @param out The stream to write to
495          */

496         protected CallScriptConduit(PrintWriter JavaDoc out)
497         {
498             super(RANK_SLOW);
499             if (out == null)
500             {
501                 throw new NullPointerException JavaDoc("out=null");
502             }
503
504             this.out = out;
505         }
506
507         /* (non-Javadoc)
508          * @see org.directwebremoting.ScriptConduit#addScript(org.directwebremoting.ScriptBuffer)
509          */

510         public boolean addScript(ScriptBuffer script) throws IOException JavaDoc, MarshallException
511         {
512             sendScript(out, ScriptBufferUtil.createOutput(script, converterManager));
513             return true;
514         }
515
516         /**
517          * The PrintWriter to send output to, and that we should synchronize against
518          */

519         private final PrintWriter JavaDoc out;
520     }
521
522     /**
523      * The session cookie name
524      */

525     protected String JavaDoc sessionCookieName = "JSESSIONID";
526
527     /**
528      * By default we disable GET, but this hinders old Safaris
529      */

530     private boolean allowGetForSafariButMakeForgeryEasier = false;
531
532     /**
533      * To we perform cross-domain session security checks?
534      */

535     protected boolean crossDomainSessionSecurity = true;
536
537     /**
538      * How we turn pages into the canonical form.
539      */

540     protected PageNormalizer pageNormalizer = null;
541
542     /**
543      * How we convert parameters
544      */

545     protected ConverterManager converterManager = null;
546
547     /**
548      * How we create new beans
549      */

550     protected CreatorManager creatorManager = null;
551
552     /**
553      * The security manager
554      */

555     protected AccessControl accessControl = null;
556
557     /**
558      * How we stash away the request
559      */

560     protected static final String JavaDoc ATTRIBUTE_REQUEST = "org.directwebremoting.dwrp.request";
561
562     /**
563      * How we stash away the conduit
564      */

565     protected static final String JavaDoc ATTRIBUTE_CONDUIT = "org.directwebremoting.dwrp.conduit";
566
567     /**
568      * How we stash away the results of the request parse
569      */

570     protected static final String JavaDoc ATTRIBUTE_BATCH = "org.directwebremoting.dwrp.batch";
571
572     /**
573      * The log stream
574      */

575     protected static final Logger log = Logger.getLogger(BaseCallMarshaller.class);
576 }
577
Popular Tags