001 package com.mockrunner.tag; 002 003 import java.io.IOException; 004 import java.util.HashMap; 005 import java.util.Map; 006 007 import javax.servlet.jsp.JspException; 008 import javax.servlet.jsp.tagext.JspTag; 009 import javax.servlet.jsp.tagext.Tag; 010 import javax.servlet.jsp.tagext.TagSupport; 011 012 import org.apache.commons.logging.Log; 013 import org.apache.commons.logging.LogFactory; 014 015 import com.mockrunner.base.HTMLOutputModule; 016 import com.mockrunner.base.NestedApplicationException; 017 import com.mockrunner.mock.web.MockJspWriter; 018 import com.mockrunner.mock.web.MockPageContext; 019 import com.mockrunner.mock.web.WebMockObjectFactory; 020 021 /** 022 * Module for custom tag tests. Simulates the container by 023 * performing the tag lifecycle. 024 */ 025 public class TagTestModule extends HTMLOutputModule 026 { 027 private final static Log log = LogFactory.getLog(TagTestModule.class); 028 private WebMockObjectFactory mockFactory; 029 private NestedTag tag; 030 031 public TagTestModule(WebMockObjectFactory mockFactory) 032 { 033 super(mockFactory); 034 this.mockFactory = mockFactory; 035 } 036 037 /** 038 * Creates a tag. Internally a {@link NestedTag} 039 * is created but the wrapped tag is returned. If you 040 * simply want to test the output of the tag without 041 * nesting other tags, you do not have to care about the 042 * {@link NestedTag}, just use the returned instance. 043 * An empty attribute <code>Map</code> will be used for 044 * the tag. 045 * @param tagClass the class of the tag 046 * @return instance of <code>TagSupport</code> or <code>BodyTagSupport</code> 047 * @throws <code>RuntimeException</code>, if the created tag 048 * is not an instance of <code>TagSupport</code> 049 */ 050 public TagSupport createTag(Class tagClass) 051 { 052 if(!TagSupport.class.isAssignableFrom(tagClass)) 053 { 054 throw new IllegalArgumentException("specified class is not an instance of TagSupport. Please use createWrappedTag"); 055 } 056 return createTag(tagClass, new HashMap()); 057 } 058 059 /** 060 * Creates a tag. Internally a {@link NestedTag} 061 * is created but the wrapped tag is returned. If you 062 * simply want to test the output of the tag without 063 * nesting other tags, you do not have to care about the 064 * {@link NestedTag}, just use the returned instance. 065 * The attributes <code>Map</code> contains the attributes 066 * of this tag (<i>propertyname</i> maps to <i>propertyvalue</i>). 067 * The attributes are populated (i.e. the tags setters are called) 068 * during the lifecycle or with an explicit call of 069 * {@link #populateAttributes}. 070 * @param tagClass the class of the tag 071 * @param attributes the attribute map 072 * @return instance of <code>TagSupport</code> or <code>BodyTagSupport</code> 073 * @throws <code>RuntimeException</code>, if the created tag 074 * is not an instance of <code>TagSupport</code> 075 */ 076 public TagSupport createTag(Class tagClass, Map attributes) 077 { 078 if(!TagSupport.class.isAssignableFrom(tagClass)) 079 { 080 throw new IllegalArgumentException("specified class is not an instance of TagSupport. Please use createWrappedTag"); 081 } 082 return createNestedTag(tagClass, attributes).getTag(); 083 } 084 085 /** 086 * Creates a tag. Internally a {@link NestedTag} 087 * is created but the wrapped tag is returned. If you 088 * simply want to test the output of the tag without 089 * nesting other tags, you do not have to care about the 090 * {@link NestedTag}, just use the returned instance. 091 * An empty attribute <code>Map</code> will be used for 092 * the tag. 093 * This method can be used for all kind of tags. The tag 094 * class does not need to be a subclass of <code>TagSupport</code>. 095 * @param tagClass the class of the tag 096 * @return instance of <code>JspTag</code> 097 */ 098 public JspTag createWrappedTag(Class tagClass) 099 { 100 return createWrappedTag(tagClass, new HashMap()); 101 } 102 103 /** 104 * Creates a tag. Internally a {@link NestedTag} 105 * is created but the wrapped tag is returned. If you 106 * simply want to test the output of the tag without 107 * nesting other tags, you do not have to care about the 108 * {@link NestedTag}, just use the returned instance. 109 * The attributes <code>Map</code> contains the attributes 110 * of this tag (<i>propertyname</i> maps to <i>propertyvalue</i>). 111 * The attributes are populated (i.e. the tags setters are called) 112 * during the lifecycle or with an explicit call of 113 * {@link #populateAttributes}. 114 * This method can be used for all kind of tags. The tag 115 * class does not need to be a subclass of <code>TagSupport</code>. 116 * @param tagClass the class of the tag 117 * @param attributes the attribute map 118 * @return instance of <code>JspTag</code> 119 */ 120 public JspTag createWrappedTag(Class tagClass, Map attributes) 121 { 122 return createNestedTag(tagClass, attributes).getWrappedTag(); 123 } 124 125 /** 126 * Creates a {@link NestedTag} and returns it. You can 127 * add child tags or body blocks to the {@link NestedTag}. 128 * Use {@link #getTag} to get the wrapped tag. 129 * An empty attribute <code>Map</code> will be used for 130 * the tag. 131 * @param tagClass the class of the tag 132 * @return instance of {@link NestedStandardTag}, {@link NestedBodyTag} or 133 * {@link NestedSimpleTag} 134 */ 135 public NestedTag createNestedTag(Class tagClass) 136 { 137 return createNestedTag(tagClass, new HashMap()); 138 } 139 140 /** 141 * Creates a {@link NestedTag} and returns it. You can 142 * add child tags or body blocks to the {@link NestedTag}. 143 * Use {@link #getTag} to get the wrapped tag. 144 * The attributes <code>Map</code> contains the attributes 145 * of this tag (<i>propertyname</i> maps to <i>propertyvalue</i>). 146 * The attributes are populated (i.e. the tags setters are called) 147 * during the lifecycle or with an explicit call of 148 * {@link #populateAttributes}. 149 * @param tagClass the class of the tag 150 * @param attributes the attribute map 151 * @return instance of {@link NestedStandardTag}, {@link NestedBodyTag} or 152 * {@link NestedSimpleTag} 153 */ 154 public NestedTag createNestedTag(Class tagClass, Map attributes) 155 { 156 try 157 { 158 this.tag = (NestedTag)TagUtil.createNestedTagInstance(tagClass, getMockPageContext(), attributes); 159 return this.tag; 160 } 161 catch(IllegalArgumentException exc) 162 { 163 throw exc; 164 } 165 catch(Exception exc) 166 { 167 log.error(exc.getMessage(), exc); 168 throw new NestedApplicationException(exc); 169 } 170 } 171 172 /** 173 * Creates a {@link NestedTag} and returns it. You can 174 * add child tags or body blocks to the {@link NestedTag}. 175 * Use {@link #getTag} to get the wrapped tag. 176 * An empty attribute <code>Map</code> will be used for 177 * the tag. 178 * @param tag the tag 179 * @return instance of {@link NestedStandardTag} or {@link NestedBodyTag} 180 */ 181 public NestedTag setTag(TagSupport tag) 182 { 183 return setTag(tag, new HashMap()); 184 } 185 186 /** 187 * Creates a {@link NestedTag} and returns it. You can 188 * add child tags or body blocks to the {@link NestedTag}. 189 * Use {@link #getTag} to get the wrapped tag. 190 * The attributes <code>Map</code> contains the attributes 191 * of this tag (<i>propertyname</i> maps to <i>propertyvalue</i>). 192 * The attributes are populated (i.e. the tags setters are called) 193 * during the lifecycle or with an explicit call of 194 * {@link #populateAttributes}. 195 * @param tag the tag 196 * @param attributes the attribute map 197 * @return instance of {@link NestedStandardTag} or {@link NestedBodyTag} 198 */ 199 public NestedTag setTag(TagSupport tag, Map attributes) 200 { 201 try 202 { 203 this.tag = (NestedTag)TagUtil.createNestedTagInstance(tag, getMockPageContext(), attributes); 204 return this.tag; 205 } 206 catch(IllegalArgumentException exc) 207 { 208 throw exc; 209 } 210 catch(Exception exc) 211 { 212 log.error(exc.getMessage(), exc); 213 throw new NestedApplicationException(exc); 214 } 215 } 216 217 /** 218 * Creates a {@link NestedTag} and returns it. You can 219 * add child tags or body blocks to the {@link NestedTag}. 220 * Use {@link #getTag} to get the wrapped tag. 221 * An empty attribute <code>Map</code> will be used for 222 * the tag. 223 * This method can be used for all kind of tags. The tag 224 * class does not need to be a subclass of <code>TagSupport</code>. 225 * @param tag the tag 226 * @return instance of {@link NestedStandardTag}, {@link NestedBodyTag} or 227 * {@link NestedSimpleTag} 228 */ 229 public NestedTag setTag(JspTag tag) 230 { 231 return setTag(tag, new HashMap()); 232 } 233 234 /** 235 * Creates a {@link NestedTag} and returns it. You can 236 * add child tags or body blocks to the {@link NestedTag}. 237 * Use {@link #getTag} to get the wrapped tag. 238 * The attributes <code>Map</code> contains the attributes 239 * of this tag (<i>propertyname</i> maps to <i>propertyvalue</i>). 240 * The attributes are populated (i.e. the tags setters are called) 241 * during the lifecycle or with an explicit call of 242 * {@link #populateAttributes}. 243 * This method can be used for all kind of tags. The tag 244 * class does not need to be a subclass of <code>TagSupport</code>. 245 * @param tag the tag 246 * @param attributes the attribute map 247 * @return instance of {@link NestedStandardTag}, {@link NestedBodyTag} or 248 * {@link NestedSimpleTag} 249 */ 250 public NestedTag setTag(JspTag tag, Map attributes) 251 { 252 try 253 { 254 this.tag = (NestedTag)TagUtil.createNestedTagInstance(tag, getMockPageContext(), attributes); 255 return this.tag; 256 } 257 catch(IllegalArgumentException exc) 258 { 259 throw exc; 260 } 261 catch(Exception exc) 262 { 263 log.error(exc.getMessage(), exc); 264 throw new NestedApplicationException(exc); 265 } 266 } 267 268 /** 269 * Specify if the <code>release</code> method should be called 270 * before populating a tag. Delegates to {@link NestedTag#setDoRelease} 271 * Defaults to <code>false</code>. It's the container behaviour to call 272 * <code>release</code>, but it's usually not necessary in the tests, 273 * because the tag instances are not reused during a test run. 274 * @param doRelease should release be called 275 */ 276 public void setDoRelease(boolean doRelease) 277 { 278 if(null == tag) 279 { 280 throw new RuntimeException("Not current tag set"); 281 } 282 tag.setDoRelease(doRelease); 283 } 284 285 /** 286 * Specify if the <code>release</code> method should be called 287 * before populating a tag. Delegates to {@link NestedTag#setDoReleaseRecursive} 288 * Defaults to <code>false</code>. It's the container behaviour to call 289 * <code>release</code>, but it's usually not necessary in the tests, 290 * because the tag instances are not reused during a test run. 291 * @param doRelease should release be called 292 */ 293 public void setDoReleaseRecursive(boolean doRelease) 294 { 295 if(null == tag) 296 { 297 throw new RuntimeException("Not current tag set"); 298 } 299 tag.setDoReleaseRecursive(doRelease); 300 } 301 302 /** 303 * Populates the attributes of the underlying tag by 304 * calling {@link NestedTag#populateAttributes}. The setters 305 * of the tag are called. Please note that child tags are not 306 * populated. This is done during the lifecycle. 307 */ 308 public void populateAttributes() 309 { 310 if(null == tag) 311 { 312 throw new RuntimeException("Not current tag set"); 313 } 314 tag.populateAttributes(); 315 } 316 317 /** 318 * Sets the body of the tag as a static string. Please 319 * note that all childs of the underlying {@link NestedTag} 320 * are deleted and the static content is set. If you want 321 * to use nested tags, please use the method {@link NestedTag#addTextChild} 322 * to set static content. 323 * @param body the static body content 324 */ 325 public void setBody(String body) 326 { 327 if(null == tag) 328 { 329 throw new RuntimeException("Not current tag set"); 330 } 331 tag.removeChilds(); 332 tag.addTextChild(body); 333 } 334 335 /** 336 * Returns the current wrapped tag. 337 * @return instance of <code>TagSupport</code> or <code>BodyTagSupport</code> 338 * @throws <code>RuntimeException</code>, if the wrapped tag 339 * is not an instance of <code>TagSupport</code> 340 */ 341 public TagSupport getTag() 342 { 343 if(null == tag) return null; 344 return tag.getTag(); 345 } 346 347 /** 348 * Returns the current wrapped tag. 349 * This method can be used for all kind of tags. The tag 350 * class does not need to be a subclass of <code>TagSupport</code>. 351 * @return instance of <code>JspTag</code> 352 */ 353 public JspTag getWrappedTag() 354 { 355 if(null == tag) return null; 356 return tag.getWrappedTag(); 357 } 358 359 /** 360 * Returns the current nested tag. You can 361 * add child tags or body blocks to the {@link NestedTag}. 362 * Use {@link #getTag} to get the wrapped tag. 363 * @return instance of {@link NestedStandardTag} or {@link NestedBodyTag} 364 */ 365 public NestedTag getNestedTag() 366 { 367 return tag; 368 } 369 370 /** 371 * Returns the <code>MockPageContext</code> object. 372 * Delegates to {@link com.mockrunner.mock.web.WebMockObjectFactory#getMockPageContext}. 373 * @return the MockPageContext 374 */ 375 public MockPageContext getMockPageContext() 376 { 377 return mockFactory.getMockPageContext(); 378 } 379 380 /** 381 * Calls the <code>doStartTag</code> method of the current tag. 382 * @throws <code>RuntimeException</code>, if the tag 383 * is not a simple tag 384 */ 385 public void doTag() 386 { 387 if(null == tag) 388 { 389 throw new RuntimeException("No current tag set"); 390 } 391 if(!isSimpleTag()) 392 { 393 throw new RuntimeException("Tag is no simple tag"); 394 } 395 try 396 { 397 ((NestedSimpleTag)tag).doTag(); 398 } 399 catch(Exception exc) 400 { 401 log.error(exc.getMessage(), exc); 402 throw new RuntimeException(exc.getMessage()); 403 } 404 } 405 406 /** 407 * Calls the <code>doStartTag</code> method of the current tag. 408 * @return the result of <code>doStartTag</code> 409 * @throws <code>RuntimeException</code>, if the tag 410 * is a simple tag 411 */ 412 public int doStartTag() 413 { 414 if(null == tag) 415 { 416 throw new RuntimeException("No current tag set"); 417 } 418 if(isSimpleTag()) 419 { 420 throw new RuntimeException("Cannot call doStartTag() on simple tags"); 421 } 422 try 423 { 424 return ((Tag)tag).doStartTag(); 425 } 426 catch(JspException exc) 427 { 428 log.error(exc.getMessage(), exc); 429 throw new RuntimeException(exc.getMessage()); 430 } 431 } 432 433 /** 434 * Calls the <code>doEndTag</code> method of the current tag. 435 * @return the result of <code>doEndTag</code> 436 * @throws <code>RuntimeException</code>, if the tag 437 * is a simple tag 438 */ 439 public int doEndTag() 440 { 441 if(null == tag) 442 { 443 throw new RuntimeException("No current tag set"); 444 } 445 if(isSimpleTag()) 446 { 447 throw new RuntimeException("Cannot call doEndTag() on simple tags"); 448 } 449 try 450 { 451 return ((Tag)tag).doEndTag(); 452 } 453 catch(JspException exc) 454 { 455 log.error(exc.getMessage(), exc); 456 throw new RuntimeException(exc.getMessage()); 457 } 458 } 459 460 /** 461 * Calls the <code>doInitBody</code> method of the current tag. 462 * @throws RuntimeException if the current tag is no body tag 463 * @throws <code>RuntimeException</code>, if the tag 464 * is a simple tag 465 */ 466 public void doInitBody() 467 { 468 if(null == tag) 469 { 470 throw new RuntimeException("No current tag set"); 471 } 472 if(!isBodyTag()) 473 { 474 throw new RuntimeException("Tag is no body tag"); 475 } 476 try 477 { 478 NestedBodyTag bodyTag = (NestedBodyTag)tag; 479 bodyTag.doInitBody(); 480 } 481 catch(JspException exc) 482 { 483 log.error(exc.getMessage(), exc); 484 throw new RuntimeException(exc.getMessage()); 485 } 486 } 487 488 /** 489 * Calls the <code>doAfterBody</code> method of the current tag. 490 * @return the result of <code>doAfterBody</code> 491 * @throws <code>RuntimeException</code>, if the tag 492 * is a simple tag 493 */ 494 public int doAfterBody() 495 { 496 if(null == tag) 497 { 498 throw new RuntimeException("No current tag set"); 499 } 500 if(isSimpleTag()) 501 { 502 throw new RuntimeException("Cannot call doAfterBody() on simple tags"); 503 } 504 try 505 { 506 return ((TagSupport)tag).doAfterBody(); 507 } 508 catch(JspException exc) 509 { 510 log.error(exc.getMessage(), exc); 511 throw new RuntimeException(exc.getMessage()); 512 } 513 } 514 515 /** 516 * Calls the <code>release</code> method of the current tag. 517 * @throws <code>RuntimeException</code>, if the tag 518 * is a simple tag 519 */ 520 public void release() 521 { 522 if(isSimpleTag()) 523 { 524 throw new RuntimeException("Cannot call release() on simple tags"); 525 } 526 ((Tag)tag).release(); 527 } 528 529 /** 530 * Performs the tags lifecycle by calling {@link NestedTag#doLifecycle}. 531 * All <code>doBody</code> and <code>doTag</code> methods are called as 532 * in the real web container. The evaluation of the body is simulated 533 * by performing the lifecycle recursively for all childs of the 534 * {@link NestedTag}. 535 * @return the result of the final <code>doEndTag</code> call or -1 in 536 * the case of a simple tag 537 */ 538 public int processTagLifecycle() 539 { 540 if(null == tag) 541 { 542 throw new RuntimeException("No current tag set"); 543 } 544 try 545 { 546 return ((NestedTag)tag).doLifecycle(); 547 } 548 catch(JspException exc) 549 { 550 log.error(exc.getMessage(), exc); 551 throw new RuntimeException(exc.getMessage()); 552 } 553 } 554 555 /** 556 * Resets the output buffer. 557 */ 558 public void clearOutput() 559 { 560 MockJspWriter writer = (MockJspWriter)mockFactory.getMockPageContext().getOut(); 561 try 562 { 563 writer.clearBuffer(); 564 } 565 catch(IOException exc) 566 { 567 log.error(exc.getMessage(), exc); 568 throw new RuntimeException(exc.getMessage()); 569 } 570 } 571 572 /** 573 * Gets the output data the current tag has rendered. Makes only sense 574 * after calling at least {@link #doStartTag} or {@link #processTagLifecycle} 575 * @return the output data 576 */ 577 public String getOutput() 578 { 579 MockJspWriter writer = (MockJspWriter)mockFactory.getMockPageContext().getOut(); 580 return writer.getOutputAsString(); 581 } 582 583 private boolean isBodyTag() 584 { 585 return (tag instanceof NestedBodyTag); 586 } 587 588 private boolean isSimpleTag() 589 { 590 return (tag instanceof NestedSimpleTag); 591 } 592 }