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 107 /** 108 * 109 * {@inheritDoc} 110 */ 111 public boolean isDynamic() 112 { 113 return true; 114 } 115 116 /** 117 * {@inheritDoc} 118 */ 119 public void renderResponse(IRequestCycle cycle) 120 throws IOException 121 { 122 _localeManager.persistLocale(); 123 124 ContentType contentType = new ContentType(CONTENT_TYPE + ";charset=" + cycle.getInfrastructure().getOutputEncoding()); 125 126 String encoding = contentType.getParameter(ENCODING_KEY); 127 128 if (encoding == null) 129 { 130 encoding = cycle.getEngine().getOutputEncoding(); 131 132 contentType.setParameter(ENCODING_KEY, encoding); 133 } 134 135 if (_writer == null) { 136 137 parseParameters(cycle); 138 139 PrintWriter printWriter = _response.getPrintWriter(contentType); 140 141 _writer = _markupWriterSource.newMarkupWriter(printWriter, contentType); 142 } 143 144 // render response 145 146 _prs = new PageRenderSupportImpl(_assetFactory, _namespace, cycle.getPage().getLocation(), this); 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 /** 316 * {@inheritDoc} 317 */ 318 public String getUniqueString(String baseValue) 319 { 320 return _prs.getUniqueString(baseValue); 321 } 322 323 /** 324 * {@inheritDoc} 325 */ 326 public void writeBodyScript(IMarkupWriter writer, IRequestCycle cycle) 327 { 328 _prs.writeBodyScript(writer, cycle); 329 } 330 331 /** 332 * {@inheritDoc} 333 */ 334 public void writeInitializationScript(IMarkupWriter writer) 335 { 336 _prs.writeInitializationScript(writer); 337 } 338 339 /** 340 * {@inheritDoc} 341 */ 342 public void beginBodyScript(IMarkupWriter normalWriter, IRequestCycle cycle) 343 { 344 _writer.begin("script"); 345 _writer.printRaw("\n//<![CDATA[\n"); 346 } 347 348 /** 349 * {@inheritDoc} 350 */ 351 public void endBodyScript(IMarkupWriter normalWriter, IRequestCycle cycle) 352 { 353 _writer.printRaw("\n//]]>\n"); 354 _writer.end(); 355 } 356 357 /** 358 * {@inheritDoc} 359 */ 360 public void writeBodyScript(IMarkupWriter normalWriter, String script, IRequestCycle cycle) 361 { 362 _writer.printRaw(script); 363 } 364 365 /** 366 * {@inheritDoc} 367 */ 368 public void writeExternalScript(IMarkupWriter normalWriter, String url, IRequestCycle cycle) 369 { 370 _writer.begin("script"); 371 _writer.attribute("type", "text/javascript"); 372 _writer.attribute("src", url); 373 _writer.end(); 374 } 375 376 /** 377 * {@inheritDoc} 378 */ 379 public void writeImageInitializations(IMarkupWriter normalWriter, String script, String preloadName, IRequestCycle cycle) 380 { 381 } 382 383 /** 384 * {@inheritDoc} 385 */ 386 public void writeInitializationScript(IMarkupWriter normalWriter, String script) 387 { 388 _writer.begin("script"); 389 390 // return is in XML so must escape any potentially non-xml compliant content 391 _writer.printRaw("\n//<![CDATA[\n"); 392 _writer.printRaw(script); 393 _writer.printRaw("\n//]]>\n"); 394 _writer.end(); 395 } 396 397 public void addStatus(IMarkupWriter normalWriter, String text) 398 { 399 throw new UnsupportedOperationException("Can't return a status response with prototype based requests."); 400 } 401 402 public void addStatusMessage(IMarkupWriter normalWriter, String category, String text) 403 { 404 throw new UnsupportedOperationException("Can't return a status response with prototype based requests."); 405 } 406 407 /** 408 * {@inheritDoc} 409 */ 410 public void render(IMarkupWriter writer, IRender render, IRequestCycle cycle) 411 { 412 // must be a valid writer already 413 414 if (NestedMarkupWriterImpl.class.isInstance(writer)) { 415 render.render(writer, cycle); 416 return; 417 } 418 419 if (IComponent.class.isInstance(render) 420 && contains((IComponent)render, ((IComponent)render).peekClientId())) 421 { 422 render.render(getComponentWriter( ((IComponent)render).peekClientId() ), cycle); 423 return; 424 } 425 426 // Nothing else found, throw out response 427 428 render.render(NullWriter.getSharedInstance(), cycle); 429 } 430 431 IMarkupWriter getComponentWriter(String id) 432 { 433 return getWriter(id, ELEMENT_TYPE); 434 } 435 436 /** 437 * 438 * {@inheritDoc} 439 */ 440 public IMarkupWriter getWriter(String id, String type) 441 { 442 Defense.notNull(id, "id can't be null"); 443 444 IMarkupWriter w = (IMarkupWriter)_writers.get(id); 445 if (w != null) 446 return w; 447 448 IMarkupWriter nestedWriter = _writer.getNestedWriter(); 449 _writers.put(id, nestedWriter); 450 451 return nestedWriter; 452 } 453 454 void beginResponse() 455 { 456 } 457 458 /** 459 * Invoked to clear out tempoary partial writer buffers before rendering exception 460 * page. 461 */ 462 void clearPartialWriters() 463 { 464 _writers.clear(); 465 } 466 467 /** 468 * Called after the entire response has been captured. Causes 469 * the writer buffer output captured to be segmented and written 470 * out to the right response elements for the client libraries to parse. 471 */ 472 void endResponse() 473 { 474 Iterator keys = _writers.keySet().iterator(); 475 476 while (keys.hasNext()) { 477 478 String key = (String)keys.next(); 479 NestedMarkupWriter nw = (NestedMarkupWriter)_writers.get(key); 480 481 nw.close(); 482 } 483 484 _writer.flush(); 485 } 486 487 /** 488 * Grabs the incoming parameters needed for json responses, most notable the 489 * {@link ServiceConstants#UPDATE_PARTS} parameter. 490 * 491 * @param cycle 492 * The request cycle to parse from 493 */ 494 void parseParameters(IRequestCycle cycle) 495 { 496 Object[] updateParts = cycle.getParameters(ServiceConstants.UPDATE_PARTS); 497 498 if (updateParts == null) 499 return; 500 501 for(int i = 0; i < updateParts.length; i++) 502 { 503 _parts.add(updateParts[i].toString()); 504 } 505 } 506 507 /** 508 * Determines if the specified component is contained in the 509 * responses requested update parts. 510 * @param target 511 * The component to check for. 512 * @return True if the request should capture the components output. 513 */ 514 public boolean contains(IComponent target) 515 { 516 if (target == null) 517 return false; 518 519 String id = target.getClientId(); 520 521 return contains(target, id); 522 } 523 524 boolean contains(IComponent target, String id) 525 { 526 if (_parts.contains(id)) 527 return true; 528 529 Iterator it = _cycle.renderStackIterator(); 530 while (it.hasNext()) { 531 532 IComponent comp = (IComponent)it.next(); 533 String compId = comp.getClientId(); 534 535 if (comp != target && _parts.contains(compId)) 536 return true; 537 } 538 539 return false; 540 } 541 542 /** 543 * {@inheritDoc} 544 */ 545 public boolean explicitlyContains(IComponent target) 546 { 547 if (target == null) 548 return false; 549 550 return _parts.contains(target.getId()); 551 } 552 }