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    }