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 }