001 package org.apache.tapestry.services.impl;
002
003 import org.apache.hivemind.Resource;
004 import org.apache.hivemind.util.Defense;
005 import org.apache.tapestry.*;
006 import org.apache.tapestry.asset.AssetFactory;
007 import org.apache.tapestry.engine.NullWriter;
008 import org.apache.tapestry.markup.MarkupWriterSource;
009 import org.apache.tapestry.markup.NestedMarkupWriterImpl;
010 import org.apache.tapestry.services.RequestLocaleManager;
011 import org.apache.tapestry.services.ResponseBuilder;
012 import org.apache.tapestry.services.ServiceConstants;
013 import org.apache.tapestry.util.ContentType;
014 import org.apache.tapestry.util.PageRenderSupportImpl;
015 import org.apache.tapestry.web.WebResponse;
016
017 import java.io.IOException;
018 import java.io.PrintWriter;
019 import java.util.*;
020
021 /**
022 * Implementation of response builder for prototype client side library initiated XHR requests.
023 *
024 */
025 public class PrototypeResponseBuilder implements ResponseBuilder {
026
027 public static final String CONTENT_TYPE = "text/html";
028
029 private final AssetFactory _assetFactory;
030
031 private final String _namespace;
032
033 private PageRenderSupportImpl _prs;
034
035 // used to create IMarkupWriter
036 private RequestLocaleManager _localeManager;
037 private MarkupWriterSource _markupWriterSource;
038 private WebResponse _response;
039
040 // our response writer
041 private IMarkupWriter _writer;
042
043 // Parts that will be updated.
044 private List _parts = new ArrayList();
045
046 // Map of specialized writers, like scripts
047
048 private Map _writers = new HashMap();
049 private IRequestCycle _cycle;
050
051 /**
052 * Used for unit testing only.
053 *
054 * @param cycle Request.
055 * @param writer Markup writer.
056 * @param parts Update parts list.
057 */
058 public PrototypeResponseBuilder(IRequestCycle cycle, IMarkupWriter writer, List parts)
059 {
060 _cycle = cycle;
061 _writer = writer;
062
063 if (parts != null)
064 _parts.addAll(parts);
065
066 _assetFactory = null;
067 _namespace = null;
068 }
069
070 /**
071 * Creates a new response builder with the required services it needs
072 * to render the response when {@link #renderResponse(IRequestCycle)} is called.
073 *
074 * @param cycle
075 * Associated request.
076 * @param localeManager
077 * Locale manager to use for response.
078 * @param markupWriterSource
079 * Creates necessary {@link IMarkupWriter} instances.
080 * @param webResponse
081 * The http response.
082 * @param assetFactory
083 * Asset manager for script / other resource inclusion.
084 * @param namespace
085 * Javascript namespace value - used in portlets.
086 */
087 public PrototypeResponseBuilder(IRequestCycle cycle,
088 RequestLocaleManager localeManager,
089 MarkupWriterSource markupWriterSource,
090 WebResponse webResponse,
091 AssetFactory assetFactory, String namespace)
092 {
093 Defense.notNull(cycle, "cycle");
094 Defense.notNull(assetFactory, "assetService");
095
096 _cycle = cycle;
097 _localeManager = localeManager;
098 _markupWriterSource = markupWriterSource;
099 _response = webResponse;
100
101 // Used by PageRenderSupport
102
103 _assetFactory = assetFactory;
104 _namespace = namespace;
105
106 _prs = new PageRenderSupportImpl(_assetFactory, _namespace, this, cycle);
107 }
108
109 /**
110 *
111 * {@inheritDoc}
112 */
113 public boolean isDynamic()
114 {
115 return true;
116 }
117
118 /**
119 * {@inheritDoc}
120 */
121 public void renderResponse(IRequestCycle cycle)
122 throws IOException
123 {
124 _localeManager.persistLocale();
125
126 ContentType contentType = new ContentType(CONTENT_TYPE + ";charset=" + cycle.getInfrastructure().getOutputEncoding());
127
128 String encoding = contentType.getParameter(ENCODING_KEY);
129
130 if (encoding == null)
131 {
132 encoding = cycle.getEngine().getOutputEncoding();
133
134 contentType.setParameter(ENCODING_KEY, encoding);
135 }
136
137 if (_writer == null)
138 {
139 parseParameters(cycle);
140
141 PrintWriter printWriter = _response.getPrintWriter(contentType);
142
143 _writer = _markupWriterSource.newMarkupWriter(printWriter, contentType);
144 }
145
146 // render response
147
148 TapestryUtils.storePageRenderSupport(cycle, _prs);
149
150 cycle.renderPage(this);
151
152 TapestryUtils.removePageRenderSupport(cycle);
153
154 endResponse();
155
156 _writer.close();
157 }
158
159 public void flush()
160 throws IOException
161 {
162 _writer.flush();
163 }
164
165 /**
166 * {@inheritDoc}
167 */
168 public void updateComponent(String id)
169 {
170 if (!_parts.contains(id))
171 _parts.add(id);
172 }
173
174 /**
175 * {@inheritDoc}
176 */
177 public IMarkupWriter getWriter()
178 {
179 return _writer;
180 }
181
182 void setWriter(IMarkupWriter writer)
183 {
184 _writer = writer;
185 }
186
187 /**
188 * {@inheritDoc}
189 */
190 public boolean isBodyScriptAllowed(IComponent target)
191 {
192 if (target != null
193 && IPage.class.isInstance(target)
194 || (IForm.class.isInstance(target)
195 && ((IForm)target).isFormFieldUpdating()))
196 return true;
197
198 return contains(target);
199 }
200
201 /**
202 * {@inheritDoc}
203 */
204 public boolean isExternalScriptAllowed(IComponent target)
205 {
206 if (target != null
207 && IPage.class.isInstance(target)
208 || (IForm.class.isInstance(target)
209 && ((IForm)target).isFormFieldUpdating()))
210 return true;
211
212 return contains(target);
213 }
214
215 /**
216 * {@inheritDoc}
217 */
218 public boolean isInitializationScriptAllowed(IComponent target)
219 {
220 if (target != null
221 && IPage.class.isInstance(target)
222 || (IForm.class.isInstance(target)
223 && ((IForm)target).isFormFieldUpdating()))
224 return true;
225
226 return contains(target);
227 }
228
229 /**
230 * {@inheritDoc}
231 */
232 public boolean isImageInitializationAllowed(IComponent target)
233 {
234 if (target != null
235 && IPage.class.isInstance(target)
236 || (IForm.class.isInstance(target)
237 && ((IForm)target).isFormFieldUpdating()))
238 return true;
239
240 return contains(target);
241 }
242
243 /**
244 * {@inheritDoc}
245 */
246 public String getPreloadedImageReference(IComponent target, IAsset source)
247 {
248 return _prs.getPreloadedImageReference(target, source);
249 }
250
251 /**
252 * {@inheritDoc}
253 */
254 public String getPreloadedImageReference(IComponent target, String url)
255 {
256 return _prs.getPreloadedImageReference(target, url);
257 }
258
259 /**
260 * {@inheritDoc}
261 */
262 public String getPreloadedImageReference(String url)
263 {
264 return _prs.getPreloadedImageReference(url);
265 }
266
267 /**
268 * {@inheritDoc}
269 */
270 public void addBodyScript(IComponent target, String script)
271 {
272 _prs.addBodyScript(target, script);
273 }
274
275 /**
276 * {@inheritDoc}
277 */
278 public void addBodyScript(String script)
279 {
280 _prs.addBodyScript(script);
281 }
282
283 /**
284 * {@inheritDoc}
285 */
286 public void addExternalScript(IComponent target, Resource resource)
287 {
288 _prs.addExternalScript(target, resource);
289 }
290
291 /**
292 * {@inheritDoc}
293 */
294 public void addExternalScript(Resource resource)
295 {
296 _prs.addExternalScript(resource);
297 }
298
299 /**
300 * {@inheritDoc}
301 */
302 public void addInitializationScript(IComponent target, String script)
303 {
304 _prs.addInitializationScript(target, script);
305 }
306
307 /**
308 * {@inheritDoc}
309 */
310 public void addInitializationScript(String script)
311 {
312 _prs.addInitializationScript(script);
313 }
314
315 public void addScriptAfterInitialization(IComponent target, String script)
316 {
317 _prs.addScriptAfterInitialization(target, script);
318 }
319
320 /**
321 * {@inheritDoc}
322 */
323 public String getUniqueString(String baseValue)
324 {
325 return _prs.getUniqueString(baseValue);
326 }
327
328 /**
329 * {@inheritDoc}
330 */
331 public void writeBodyScript(IMarkupWriter writer, IRequestCycle cycle)
332 {
333 _prs.writeBodyScript(writer, cycle);
334 }
335
336 /**
337 * {@inheritDoc}
338 */
339 public void writeInitializationScript(IMarkupWriter writer)
340 {
341 _prs.writeInitializationScript(writer);
342 }
343
344 /**
345 * {@inheritDoc}
346 */
347 public void beginBodyScript(IMarkupWriter normalWriter, IRequestCycle cycle)
348 {
349 _writer.begin("script");
350 _writer.printRaw("\n//<![CDATA[\n");
351 }
352
353 /**
354 * {@inheritDoc}
355 */
356 public void endBodyScript(IMarkupWriter normalWriter, IRequestCycle cycle)
357 {
358 _writer.printRaw("\n//]]>\n");
359 _writer.end();
360 }
361
362 /**
363 * {@inheritDoc}
364 */
365 public void writeBodyScript(IMarkupWriter normalWriter, String script, IRequestCycle cycle)
366 {
367 _writer.printRaw(script);
368 }
369
370 /**
371 * {@inheritDoc}
372 */
373 public void writeExternalScript(IMarkupWriter normalWriter, String url, IRequestCycle cycle)
374 {
375 _writer.begin("script");
376 _writer.attribute("type", "text/javascript");
377 _writer.attribute("src", url);
378 _writer.end();
379 }
380
381 /**
382 * {@inheritDoc}
383 */
384 public void writeImageInitializations(IMarkupWriter normalWriter, String script, String preloadName, IRequestCycle cycle)
385 {
386 }
387
388 /**
389 * {@inheritDoc}
390 */
391 public void writeInitializationScript(IMarkupWriter normalWriter, String script)
392 {
393 _writer.begin("script");
394
395 // return is in XML so must escape any potentially non-xml compliant content
396 _writer.printRaw("\n//<![CDATA[\n");
397 _writer.printRaw(script);
398 _writer.printRaw("\n//]]>\n");
399 _writer.end();
400 }
401
402 public void addStatus(IMarkupWriter normalWriter, String text)
403 {
404 throw new UnsupportedOperationException("Can't return a status response with prototype based requests.");
405 }
406
407 public void addStatusMessage(IMarkupWriter normalWriter, String category, String text)
408 {
409 throw new UnsupportedOperationException("Can't return a status response with prototype based requests.");
410 }
411
412 /**
413 * {@inheritDoc}
414 */
415 public void render(IMarkupWriter writer, IRender render, IRequestCycle cycle)
416 {
417 // must be a valid writer already
418
419 if (NestedMarkupWriterImpl.class.isInstance(writer))
420 {
421 render.render(writer, cycle);
422 return;
423 }
424
425 if (IComponent.class.isInstance(render)
426 && contains((IComponent)render, ((IComponent)render).peekClientId()))
427 {
428 render.render(getComponentWriter( ((IComponent)render).peekClientId() ), cycle);
429 return;
430 }
431
432 // Nothing else found, throw out response
433
434 render.render(NullWriter.getSharedInstance(), cycle);
435 }
436
437 IMarkupWriter getComponentWriter(String id)
438 {
439 return getWriter(id, ELEMENT_TYPE);
440 }
441
442 /**
443 *
444 * {@inheritDoc}
445 */
446 public IMarkupWriter getWriter(String id, String type)
447 {
448 Defense.notNull(id, "id can't be null");
449
450 IMarkupWriter w = (IMarkupWriter)_writers.get(id);
451 if (w != null)
452 return w;
453
454 IMarkupWriter nestedWriter = _writer.getNestedWriter();
455 _writers.put(id, nestedWriter);
456
457 return nestedWriter;
458 }
459
460 void beginResponse()
461 {
462 }
463
464 /**
465 * Invoked to clear out tempoary partial writer buffers before rendering exception
466 * page.
467 */
468 void clearPartialWriters()
469 {
470 _writers.clear();
471 }
472
473 /**
474 * Called after the entire response has been captured. Causes
475 * the writer buffer output captured to be segmented and written
476 * out to the right response elements for the client libraries to parse.
477 */
478 void endResponse()
479 {
480 Iterator keys = _writers.keySet().iterator();
481
482 while (keys.hasNext())
483 {
484 String key = (String)keys.next();
485 NestedMarkupWriter nw = (NestedMarkupWriter)_writers.get(key);
486
487 nw.close();
488 }
489
490 _writer.flush();
491 }
492
493 /**
494 * Grabs the incoming parameters needed for json responses, most notable the
495 * {@link ServiceConstants#UPDATE_PARTS} parameter.
496 *
497 * @param cycle
498 * The request cycle to parse from
499 */
500 void parseParameters(IRequestCycle cycle)
501 {
502 Object[] updateParts = cycle.getParameters(ServiceConstants.UPDATE_PARTS);
503
504 if (updateParts == null)
505 return;
506
507 for(int i = 0; i < updateParts.length; i++)
508 {
509 _parts.add(updateParts[i].toString());
510 }
511 }
512
513 /**
514 * Determines if the specified component is contained in the
515 * responses requested update parts.
516 * @param target
517 * The component to check for.
518 * @return True if the request should capture the components output.
519 */
520 public boolean contains(IComponent target)
521 {
522 if (target == null)
523 return false;
524
525 String id = target.getClientId();
526
527 return contains(target, id);
528 }
529
530 boolean contains(IComponent target, String id)
531 {
532 if (_parts.contains(id))
533 return true;
534
535 Iterator it = _cycle.renderStackIterator();
536 while (it.hasNext())
537 {
538 IComponent comp = (IComponent)it.next();
539 String compId = comp.getClientId();
540
541 if (comp != target && _parts.contains(compId))
542 return true;
543 }
544
545 return false;
546 }
547
548 /**
549 * {@inheritDoc}
550 */
551 public boolean explicitlyContains(IComponent target)
552 {
553 if (target == null)
554 return false;
555
556 return _parts.contains(target.getId());
557 }
558 }