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 }