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 * after processing the tag lifecycle. 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 * after processing the tag lifecycle. 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 NestedApplicationException(exc);
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 NestedApplicationException(exc);
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 NestedApplicationException(exc);
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 NestedApplicationException(exc);
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 NestedApplicationException(exc);
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 NestedApplicationException(exc);
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 NestedApplicationException(exc);
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 }