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