2011/08/05 - Jakarta Cactus has been retired.

For more information, please explore the Attic.

View Javadoc

1   /* 
2    * ========================================================================
3    * 
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   * 
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * 
19   * ========================================================================
20   */
21  package org.apache.cactus.extension.jsp;
22  
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Iterator;
26  import java.util.List;
27  
28  import junit.framework.Assert;
29  
30  import javax.servlet.jsp.JspException;
31  import javax.servlet.jsp.PageContext;
32  import javax.servlet.jsp.tagext.BodyContent;
33  import javax.servlet.jsp.tagext.BodyTag;
34  import javax.servlet.jsp.tagext.IterationTag;
35  import javax.servlet.jsp.tagext.Tag;
36  import javax.servlet.jsp.tagext.TryCatchFinally;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  
41  /**
42   * Convenience class that supports the testing of JSP tag by managing the tag's
43   * lifecycle as required by the JSP specification.
44   * 
45   * <p>
46   *   This class is basically a stub implementation of the tag management
47   *   facilities that an actual JSP container would provide. The implementation
48   *   attempts to follow the specification as closely as possible, but the tag
49   *   handling functionality of real JSP implementations may vary in some
50   *   details.
51   * </p>
52   * 
53   * <p>
54   *   Although this class works quite well when used in the test methods of a 
55   *   {@link org.apache.cactus.JspTestCase JspTestCase}, it can also safely be
56   *   used outside of the Cactus testing framework, for example when following
57   *   a mock objects approach.
58   * </p>
59   * 
60   * <h4>Testing Simple Tags</h4>
61   * <p>
62   *   This is how you would use this class when testing the
63   *   <code>&lt;c:set&gt;</code>-tag of the JSTL reference implementation:
64   *   <blockquote><pre>
65    SetTag tag = new SetTag();
66    JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
67    tag.setVar("name");
68    tag.setValue("value");
69    lifecycle.invoke();
70    assertEquals("value", pageContext.findAttribute("name"));</pre>
71   *   </blockquote>
72   *   The order is important:
73   *   <ol>
74   *     <li>
75   *       Instantiation of the tag under test
76   *     </li>
77   *     <li>
78   *       Instantiation of the lifecycle helper, passing in the page context and
79   *       the tag instance
80   *     </li>
81   *     <li>
82   *       Set the tag's attributes
83   *     </li>
84   *     <li>
85   *       Start the tag's lifecycle by calling
86   *       {@link #invoke() JspTagLifecycle.invoke()}
87   *     </li>
88   *     <li>
89   *       Make assertions
90   *     </li>
91   *   </ol>
92   * </p>
93   * 
94   * <h4>Adding Expectations to the Lifecycle</h4>
95   * <p>
96   *   <code>JspTagLifecycle</code> features a couple of methods that let you 
97   *   easily add expectations about the tag's lifecycle to the test. For example,
98   *   the method {@link #expectBodySkipped expectBodySkipped()} can be used to 
99   *   verify that tag's body is not evaluated under the conditions set up by the
100  *   test:
101  *   <pre>
102  * IfTag tag = new IfTag();
103  * JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
104  * tag.setTest("false");
105  * lifecycle.expectBodySkipped();
106  * lifecycle.invoke();</pre>
107  * </p>
108  * <p>
109  *   An example of a more sophisticated expectationion is the
110  *   {@link #expectScopedVariableExposed(String, Object[])}
111  *   method, which can verify that a specific scoped variable gets exposed in
112  *   the body of the tag, and that the exposed variable has a specific value in
113  *   each iteration step:
114  *   <pre>
115  * ForEachTag tag = new ForEachTag();
116  * JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
117  * tag.setVar("item");
118  * tag.setItems("One,Two,Three");
119  * lifecycle.expectBodyEvaluated(3);
120  * lifecycle.expectScopedVariableExposed(
121  *     "item", new Object[] {"One", "Two", "Three"});
122  * lifecycle.invoke();</pre>
123  * </p>
124  * 
125  * <h4>Custom Expectations</h4>
126  * <p>
127  *   In some cases, using the expectations offered by 
128  *   <code>JspTagLifecycle</code> does not suffice. In such cases, you need to 
129  *   use custom expectations. You can add custom expectations by creating a 
130  *   concrete subclass of the {@link JspTagLifecycle.Interceptor Interceptor}
131  *   class, and adding it to the list of the tag lifecycles interceptors through
132  *   {@link JspTagLifecycle#addInterceptor addInterceptor()}:
133  *   <pre>
134  * ForEachTag tag = new ForEachTag();
135  * JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
136  * tag.setVarStatus("status");
137  * tag.setBegin("0");
138  * tag.setEnd("2");
139  * lifecycle.addInterceptor(new JspTagLifecycle.Interceptor() {
140  *     public void evalBody(int theIteration, BodyContent theBody) {
141  *         LoopTagStatus status = (LoopTagStatus)
142  *             pageContext.findAttribute("status");
143  *         assertNotNull(status);
144  *         if (theIteration == 0) {
145  *             assertTrue(status.isFirst());
146  *             assertFalse(status.isLast());
147  *         }
148  *         else if (theIteration == 1) {
149  *             assertFalse(status.isFirst());
150  *             assertFalse(status.isLast());
151  *         }
152  *         else if (theIteration == 2) {
153  *             assertFalse(status.isFirst());
154  *             assertTrue(status.isLast());
155  *         }
156  *     }
157  * });
158  * lifecycle.invoke();</pre>
159  * </p>
160  * 
161  * <h4>Specifying Nested Content</h4>
162  * <p>
163  *   <code>JspTagLifecycle</code> let's you add nested tempate text as well as 
164  *   nested tags to the tag under test. The most important use of this feature 
165  *   is testing of collaboration between tags, but it also allows you to easily
166  *   check whether a tag correctly handles its body content.
167  * </p>
168  * <p>
169  *   The following example demonstrates how to add nested template text to the 
170  *   tag, and how to assert that the body was written to the HTTP response on
171  *   the client side:
172  *   <pre>
173  * public void testOutTagDefaultBody() throws JspException, IOException {
174  *     OutTag tag = new OutTag();
175  *     JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
176  *     tag.setValue(null);
177  *     lifecycle.addNestedText("Default");
178  *     lifecycle.expectBodyEvaluated();
179  *     lifecycle.invoke();
180  * }
181  * public void endOutTagDefaultBody(WebResponse theResponse) {
182  *     String output = theResponse.getText();
183  *     assertEquals("Default", output);
184  * }</pre>
185  * </p>
186  * <p>
187  *   In sophisticated tag libraries, there will be many cases where tags need 
188  *   to collaborate with each other in some way. This is usually done by nesting
189  *   such tags within eachother. <code>JspTagLifecycle</code> supports such 
190  *   scenarios by allowing you to add nested tags to an existing tag lifecycle.
191  *   The nested tags can than be decorated with expectations themselves, as you
192  *   can see in the following example:
193  *   <pre>
194  * ChooseTag chooseTag = new ChooseTag();
195  * JspTagLifecycle chooseLifecycle =
196  *     new JspTagLifecycle(pageContext, chooseTag);
197  * WhenTag whenTag = new WhenTag();
198  * JspTagLifecycle whenLifecycle =
199  *     chooseLifecycle.addNestedTag(whenTag);
200  * whenTag.setTest("false");
201  * whenLifecycle.expectBodySkipped();
202  * OtherwiseTag otherwiseTag = new OtherwiseTag();
203  * JspTagLifecycle otherwiseLifecycle =
204  *     chooseLifecycle.addNestedTag(otherwiseTag);
205  * otherwiseLifecycle.expectBodyEvaluated();
206  * chooseLifecycle.invoke();</pre>
207  *   The code above creates a constellation of tags equivalent to the following
208  *   JSP fragment:
209  *   <pre>
210  * &lt;c:choose&gt;
211  *  &lt;c:when test='false'&gt;
212  *   &lt;%-- body content not significant for the test --%&gt;
213  *  &lt;/c:when&gt;
214  *  &lt;c:otherwise&gt;
215  *   &lt;%-- body content not significant for the test --%&gt;
216  *  &lt;/c:otherwise&gt;
217  * &lt;/c:choose&gt;</pre>
218  * </p>
219  * 
220  * @since Cactus 1.5
221  * 
222  * @version $Id: JspTagLifecycle.java 238991 2004-05-22 11:34:50Z vmassol $
223  * @see org.apache.cactus.JspTestCase
224  */
225 public final class JspTagLifecycle
226 {   
227     // Inner Classes -----------------------------------------------------------
228     
229     /**
230      * Abstract class for intercepting the tag lifecycle. You can override any
231      * of the methods to insert expectations about the tag's behaviour while it
232      * is being executed.
233      * 
234      * @since Cactus 1.5
235      */
236     public abstract static class Interceptor
237     {
238         
239         /**
240          * Method called when the body of the tag would be evaluated. Can be
241          * used in specific test cases to perform assertions.
242          * 
243          * Please note that if you're testing a <code>BodyTag</code>, you
244          * should not write content to the
245          * {@link org.apache.cactus.JspTestCase#out} instance variable while 
246          * the body is being evaluated. This is because the actual implicit
247          * object <code>out</code> in JSP pages gets replaced by the current 
248          * nested <code>BodyContent</code>, whereas in <code>JspTestCase</code>
249          * the <code>out</code> variable always refers to the top level
250          * <code>JspWriter</code>. Instead, simply use the 
251          * <code>BodyContent</code> parameter passed into the
252          * {@link JspTagLifecycle.Interceptor#evalBody evalBody()} method or 
253          * the <code>JspWriter</code> retrieved by a call to 
254          * {javax.servlet.jsp.PageContext#getOut pageContext.getOut()}. 
255          * 
256          * @param theIteration The number of times the body has been evaluated
257          * @param theBody The body content, or <tt>null</tt> if the tag isn't a
258          *        <tt>BodyTag</tt>
259          * @throws JspException If thrown by a nested tag
260          * @throws IOException If an error occurs when reading or writing the
261          *         body content
262          */
263         public void evalBody(int theIteration, BodyContent theBody)
264             throws JspException, IOException
265         {
266             // default implementation does nothing
267         }
268         
269         /**
270          * Method called when the body of the tag would be skipped. Can be used 
271          * in specific test cases to perform assertions.
272          */
273         public void skipBody()
274         {
275             // default implementation does nothing
276         }
277         
278     }
279     
280     /**
281      * A specialized interceptor that verifies that the tag's body is evaluated
282      * at least once.
283      * 
284      * @since Cactus 1.5
285      */
286     private static class ExpectBodyEvaluatedInterceptor
287         extends Interceptor
288     {
289         /**
290          * The actual number of times the tag's body has been evaluated.
291          */
292         private int actualNumIterations;
293         
294         /**
295          * The number of times the tag's body is expected to be evaluated.
296          */
297         private int expectedNumIterations;
298         
299         /**
300          * Constructor.
301          * 
302          * @param theNumIterations The number of iterations expected
303          */
304         public ExpectBodyEvaluatedInterceptor(int theNumIterations)
305         {
306             this.expectedNumIterations = theNumIterations;
307         }
308         
309         /**
310          * Overridden to assert that the body doesn't get evaluated more often
311          * than expected.
312          * 
313          * {@inheritDoc}
314          * @see JspTagLifecycle.Interceptor#evalBody(int,BodyContent)
315          */
316         public void evalBody(int theIteration, BodyContent theBody)
317         {
318             actualNumIterations++;
319             if (actualNumIterations > expectedNumIterations)
320             {
321                 Assert.fail("Expected " + expectedNumIterations
322                     + " iterations, but was " + actualNumIterations);
323             }
324         }
325         
326         /**
327          * Overridden to assert that the body got evaluated as often as
328          * expected.
329          */
330         public void skipBody()
331         {
332             if (actualNumIterations < expectedNumIterations)
333             {
334                 Assert.fail("Expected " + expectedNumIterations
335                     + " iterations, but was " + actualNumIterations);
336             }
337         }
338     }
339     
340     /**
341      * A specialized interceptor that asserts that the tag's body is skipped.
342      * 
343      * @since Cactus 1.5
344      */
345     private static class ExpectBodySkippedInterceptor
346         extends Interceptor
347     {
348         /**
349          * Overridden to assert that the body doesn't get evaluated.
350          * 
351          * {@inheritDoc}
352          * @see JspTagLifecycle.Interceptor#evalBody(int,BodyContent)
353          */
354         public void evalBody(int theIteration, BodyContent theBody)
355         {
356             Assert.fail("Tag body should have been skipped");
357         }
358     }
359     
360     /**
361      * A specialized interceptor that checks whether a specific scoped variable
362      * is exposed in the body of the tag with a specific value.
363      * 
364      * @since Cactus 1.5
365      */
366     private class ExpectScopedVariableExposedInterceptor
367         extends Interceptor
368     {
369         /**
370          * The name of the scoped variable.
371          */
372         private String name;
373         
374         /**
375          * The list of expected values of the variable.
376          */
377         private Object[] expectedValues;
378         
379         /**
380          * The scope in which the variable is stored.
381          */
382         private int scope;
383         
384         /**
385          * Constructor.
386          * 
387          * @param theName The name of the scoped variable to check for
388          * @param theExpectedValues An array containing the expected values, 
389          *        one item for every iteration step
390          * @param theScope The scope to search for the scoped variable
391          */
392         public ExpectScopedVariableExposedInterceptor(String theName,
393             Object[] theExpectedValues, int theScope)
394         {
395             this.name = theName;
396             this.expectedValues = theExpectedValues;
397             this.scope = theScope;
398         }
399         
400         /**
401          * Overridden to assert that the scoped variable is exposed as expected.
402          * 
403          * {@inheritDoc}
404          * @see JspTagLifecycle.Interceptor#evalBody(int,BodyContent)
405          */
406         public void evalBody(int theIteration, BodyContent theBody)
407         {
408             Assert.assertEquals(expectedValues[theIteration],
409                 pageContext.getAttribute(name, scope));
410         }
411     }
412     
413     /**
414      * A specialized interceptor that invokes the lifecycle of a nested tag.
415      * 
416      * @since Cactus 1.5
417      */
418     private class NestedTagInterceptor
419         extends Interceptor
420     {
421         /**
422          * The lifecycle object of the nested tag.
423          */
424         private JspTagLifecycle lifecycle;
425         
426         /**
427          * Constructor.
428          * 
429          * @param theLifecycle The lifecycle instance associated with the nested
430          *        tag
431          */
432         public NestedTagInterceptor(JspTagLifecycle theLifecycle)
433         {
434             this.lifecycle = theLifecycle;
435         }
436         
437         /**
438          * Overridden to invoke the lifecycle of the nested tag.
439          * 
440          * {@inheritDoc}
441          * @see JspTagLifecycle.Interceptor#evalBody(int,BodyContent)
442          */
443         public void evalBody(int theIteration, BodyContent theBody)
444             throws JspException, IOException
445         {
446             lifecycle.invoke();
447         }
448     }
449     
450     /**
451      * A specialized interceptor that prints nested template text when the tag's
452      * body is evaluated.
453      * 
454      * @since Cactus 1.5
455      */
456     private class NestedTextInterceptor
457         extends Interceptor
458     {
459         /**
460          * The nested text.
461          */
462         private String text;
463         
464         /**
465          * Constructor.
466          * 
467          * @param theText The nested text
468          */
469         public NestedTextInterceptor(String theText)
470         {
471             this.text = theText;
472         }
473         
474         /**
475          * Overridden to write the nested text to the current writer.
476          * 
477          * {@inheritDoc}
478          * @see JspTagLifecycle.Interceptor#evalBody(int,BodyContent)
479          */
480         public void evalBody(int theIteration, BodyContent theBody)
481             throws IOException
482         {
483             if (theBody != null)
484             {
485                 theBody.print(text);
486             }
487             else
488             {
489                 pageContext.getOut().print(text);
490             }
491         }
492     }
493     
494     // Class Variables ---------------------------------------------------------
495     
496     /**
497      * The log target.
498      */
499     private static Log log = LogFactory.getLog(JspTagLifecycle.class);
500     
501     // Instance Variables ------------------------------------------------------
502     
503     /**
504      * The JSP page context.
505      */
506     protected PageContext pageContext;
507     
508     /**
509      * The JSP tag handler.
510      */
511     private Tag tag;
512         
513     /**
514      * The interceptor chain.
515      */
516     private List interceptors;
517     
518     // Constructors ------------------------------------------------------------
519     
520     /**
521      * Constructor.
522      * 
523      * @param thePageContext The JSP page context
524      * @param theTag The JSP tag
525      */
526     public JspTagLifecycle(PageContext thePageContext, Tag theTag)
527     {
528         if ((thePageContext == null) || (theTag == null))
529         {
530             throw new NullPointerException();
531         }
532         this.tag = theTag;
533         this.pageContext = thePageContext;
534         this.tag.setPageContext(this.pageContext);
535     }
536     
537     // Public Methods ----------------------------------------------------------
538     
539     /**
540      * Adds an interceptor to the interceptor chain.
541      * 
542      * @param theInterceptor The interceptor to add
543      */
544     public void addInterceptor(Interceptor theInterceptor)
545     {
546         if (theInterceptor == null)
547         {
548             throw new NullPointerException();
549         }
550         if (this.interceptors == null)
551         {
552             this.interceptors = new ArrayList();
553         }
554         this.interceptors.add(theInterceptor);
555     }
556     
557     /**
558      * Adds a nested tag. The tag will be invoked when the body content of the
559      * enclosing tag is evaluated.
560      * 
561      * @return The lifecycle wrapper for the nested tag, can be used to add 
562      *         expectations for the nested tag
563      * @param theNestedTag The tag to be nested
564      */
565     public JspTagLifecycle addNestedTag(Tag theNestedTag)
566     {
567         if (theNestedTag == null)
568         {
569             throw new NullPointerException();
570         }
571         JspTagLifecycle lifecycle =
572             new JspTagLifecycle(this.pageContext, theNestedTag);
573         theNestedTag.setParent(this.tag);
574         addInterceptor(new NestedTagInterceptor(lifecycle));
575         return lifecycle;
576     }
577     
578     /**
579      * Adds template text to nest inside the tag. The text will be printed to 
580      * the body content when it is evaluated.
581      * 
582      * @param theNestedText The string containing the template text
583      */
584     public void addNestedText(String theNestedText)
585     {
586         if (theNestedText == null)
587         {
588             throw new NullPointerException();
589         }
590         addInterceptor(new NestedTextInterceptor(theNestedText));
591     }
592     
593     /**
594      * Adds the expectation that the tag body must be evaluated once in the 
595      * course of the tags lifecycle.
596      */
597     public void expectBodyEvaluated()
598     {
599         addInterceptor(new ExpectBodyEvaluatedInterceptor(1));
600     }
601     
602     /**
603      * Adds the expectation that the tag body must be evaluated a specific
604      * number of times in the course of the tags lifecycle.
605      * 
606      * @param theNumIterations The number of times the body is expected to get 
607      *        evaluated
608      */
609     public void expectBodyEvaluated(int theNumIterations)
610     {
611         addInterceptor(new ExpectBodyEvaluatedInterceptor(theNumIterations));
612     }
613     
614     /**
615      * Adds the expectation that the tag body must be skipped. Essentially, this
616      * expectation verifies that the tag returns <code>SKIP_BODY</code> from
617      * <code>doStartTag()</code>.
618      */
619     public void expectBodySkipped()
620     {
621         addInterceptor(new ExpectBodySkippedInterceptor());
622     }
623     
624     /**
625      * Adds a special expectation that verifies that a specific scoped variable
626      * is exposed in the body of the tag.
627      * 
628      * @param theName The name of the variable
629      * @param theExpectedValues An ordered list containing the expected values 
630      *        values of the scoped variable, one for each expected iteration
631      *        step
632      */
633     public void expectScopedVariableExposed(String theName,
634         Object[] theExpectedValues)
635     {
636         expectScopedVariableExposed(theName, theExpectedValues,
637             PageContext.PAGE_SCOPE);
638     }
639     
640     /**
641      * Adds a special expectation that verifies that a specific scoped variable
642      * is exposed in the body of the tag.
643      * 
644      * @param theName The name of the variable
645      * @param theExpectedValues An ordered list containing the expected values 
646      *        values of the scoped variable, one for each expected iteration
647      *        step
648      * @param theScope The scope under which the variable is stored
649      */
650     public void expectScopedVariableExposed(String theName,
651         Object[] theExpectedValues, int theScope)
652     {
653         if ((theName == null) || (theExpectedValues == null))
654         {
655             throw new NullPointerException();
656         }
657         if (theExpectedValues.length == 0)
658         {
659             throw new IllegalArgumentException();
660         }
661         if ((theScope != PageContext.PAGE_SCOPE)
662          && (theScope != PageContext.REQUEST_SCOPE)
663          && (theScope != PageContext.SESSION_SCOPE)
664          && (theScope != PageContext.APPLICATION_SCOPE))
665         {
666             throw new IllegalArgumentException();
667         }
668         addInterceptor(
669             new ExpectScopedVariableExposedInterceptor(theName,
670                 theExpectedValues, theScope));
671     }
672     
673     /**
674      * Invokes the tag with the provided interceptor. The tag should have been
675      * populated with its properties before calling this method. The tag is not
676      * released after the tag's lifecycle is over.
677      * 
678      * @throws JspException If the tag throws an exception
679      * @throws IOException If an error occurs when reading or writing the body
680      *         content
681      */
682     public void invoke() throws JspException, IOException
683     {
684         if (this.tag instanceof TryCatchFinally)
685         {
686             TryCatchFinally tryCatchFinally = (TryCatchFinally) this.tag;
687             try
688             {
689                 invokeInternal();
690             }
691             catch (Throwable t1)
692             {
693                 try
694                 {
695                     tryCatchFinally.doCatch(t1);
696                 }
697                 catch (Throwable t2)
698                 {
699                     throw new JspException(t2.getMessage());
700                 }
701             }
702             finally
703             {
704                 tryCatchFinally.doFinally();
705             }
706         }
707         else
708         {
709             invokeInternal();
710         }
711     }
712     
713     // Private Methods ---------------------------------------------------------
714     
715     /**
716      * Notify all interceptors about a body evaluation.
717      * 
718      * @param theIteration The iteration
719      * @param theBody The body content
720      * @throws JspException If thrown by a nested tag
721      * @throws IOException If an error occurs when reading or writing the body
722      *         content
723      */
724     private void fireEvalBody(int theIteration, BodyContent theBody)
725         throws JspException, IOException
726     {
727         if (this.interceptors != null)
728         {
729             for (Iterator i = this.interceptors.iterator(); i.hasNext();)
730             {
731                 ((Interceptor) i.next()).evalBody(theIteration, theBody);
732             }
733         }
734     }
735     
736     /**
737      * Notify all interceptors that the body has been skipped.
738      */
739     private void fireSkipBody()
740     {
741         if (this.interceptors != null)
742         {
743             for (Iterator i = this.interceptors.iterator(); i.hasNext();)
744             {
745                 ((Interceptor) i.next()).skipBody();
746             }
747         }
748     }
749     
750     /**
751      * Internal method to invoke a tag without doing exception handling.
752      * 
753      * @throws JspException If the tag throws an exception
754      * @throws IOException If an error occurs when reading or writing the body
755      *         content
756      */
757     private void invokeInternal()
758         throws JspException, IOException
759     {
760         int status = this.tag.doStartTag();
761         if (this.tag instanceof IterationTag)
762         {
763             if (status != Tag.SKIP_BODY)
764             {
765                 BodyContent body = null;
766                 try
767                 {
768                     IterationTag iterationTag = (IterationTag) this.tag;
769                     if ((status == BodyTag.EVAL_BODY_BUFFERED)
770                         && (this.tag instanceof BodyTag))
771                     {
772                         BodyTag bodyTag = (BodyTag) this.tag;
773                         body = pageContext.pushBody();
774                         if (log.isDebugEnabled())
775                         {
776                             log.debug("Pushed body content ["
777                                 + body.getString() + "]");
778                         }
779                         bodyTag.setBodyContent(body);
780                         bodyTag.doInitBody();
781                     }
782                     int iteration = 0;
783                     do
784                     {
785                         fireEvalBody(iteration, body);
786                         if (log.isDebugEnabled())
787                         {
788                             log.debug("Body evaluated for the ["
789                                 + iteration + "] time");
790                         }
791                         status = iterationTag.doAfterBody();
792                         iteration++;
793                     } while (status == IterationTag.EVAL_BODY_AGAIN);
794                     if (log.isDebugEnabled())
795                     {
796                         log.debug("Body skipped");
797                     }
798                     fireSkipBody();
799                 }
800                 finally
801                 {
802                     if (body != null)
803                     {
804                         if (log.isDebugEnabled())
805                         {
806                             log.debug("Popping body content ["
807                                 + body.getString() + "]");
808                         }
809                         pageContext.popBody();
810                         body = null;
811                     }
812                 }
813             }
814             else
815             {
816                 if (log.isDebugEnabled())
817                 {
818                     log.debug("Body skipped");
819                 }
820                 fireSkipBody();
821             }
822         }
823         status = tag.doEndTag();
824     }
825     
826 }