001    package com.mockrunner.tag;
002    
003    import java.io.IOException;
004    import java.util.Iterator;
005    import java.util.List;
006    import java.util.Map;
007    
008    import javax.servlet.jsp.JspContext;
009    import javax.servlet.jsp.JspException;
010    import javax.servlet.jsp.PageContext;
011    import javax.servlet.jsp.tagext.BodyTag;
012    import javax.servlet.jsp.tagext.DynamicAttributes;
013    import javax.servlet.jsp.tagext.SimpleTag;
014    import javax.servlet.jsp.tagext.Tag;
015    import javax.servlet.jsp.tagext.TryCatchFinally;
016    
017    import org.apache.commons.beanutils.BeanUtils;
018    import org.apache.commons.beanutils.PropertyUtils;
019    import org.apache.commons.logging.Log;
020    import org.apache.commons.logging.LogFactory;
021    
022    import com.mockrunner.base.NestedApplicationException;
023    import com.mockrunner.util.common.StringUtil;
024    
025    /**
026     * Util class for tag test framework.
027     * Please note, that the methods of this class take
028     * <code>Object</code> parameters where <code>JspTag</code>
029     * or <code>JspContext</code> would be suitable. The reason is,
030     * that these classes do not exist in J2EE 1.3. This class is
031     * usable with J2EE 1.3 and J2EE 1.4.
032     */
033    public class TagUtil
034    {
035        private final static Log log = LogFactory.getLog(TagUtil.class);
036        
037        /**
038         * Creates an {@link com.mockrunner.tag.NestedTag} instance wrapping the
039         * specified tag. Returns an instance of {@link com.mockrunner.tag.NestedStandardTag}
040         * or {@link com.mockrunner.tag.NestedBodyTag} depending on the
041         * type of specified tag.
042         * @param tag the tag class
043         * @param pageContext the corresponding <code>PageContext</code> or <code>JspContext</code>
044         * @param attributes the attribute map
045         * @return the instance of {@link com.mockrunner.tag.NestedTag}
046         * @throws IllegalArgumentException if <code>tag</code> is <code>null</code>
047         */
048        public static Object createNestedTagInstance(Class tag, Object pageContext, Map attributes)
049        {
050            if(null == tag) throw new IllegalArgumentException("tag must not be null");
051            Object tagObject;
052            try
053            {
054                tagObject = tag.newInstance();
055            }
056            catch(Exception exc)
057            {
058                log.error(exc.getMessage(), exc);
059                throw new NestedApplicationException(exc);
060            }
061            return createNestedTagInstance(tagObject, pageContext, attributes);
062        }
063        
064        /**
065         * Creates an {@link com.mockrunner.tag.NestedTag} instance wrapping the
066         * specified tag. Returns an instance of {@link com.mockrunner.tag.NestedStandardTag}
067         * or {@link com.mockrunner.tag.NestedBodyTag} depending on the
068         * type of specified tag.
069         * @param tag the tag
070         * @param pageContext the corresponding <code>PageContext</code> or <code>JspContext</code>
071         * @param attributes the attribute map
072         * @return the instance of {@link com.mockrunner.tag.NestedTag}
073         * @throws IllegalArgumentException if <code>tag</code> is <code>null</code>
074         */
075        public static Object createNestedTagInstance(Object tag, Object pageContext, Map attributes)
076        {
077            if(null == tag) throw new IllegalArgumentException("tag must not be null");
078            Object nestedTag = null;
079            if(tag instanceof BodyTag)
080            {
081                checkPageContext(pageContext);
082                nestedTag = new NestedBodyTag((BodyTag)tag, (PageContext)pageContext, attributes);
083            }
084            else if(tag instanceof Tag)
085            {
086                checkPageContext(pageContext);
087                nestedTag = new NestedStandardTag((Tag)tag, (PageContext)pageContext, attributes);
088            }
089            else if(tag instanceof SimpleTag)
090            {
091                checkJspContext(pageContext);
092                nestedTag = new NestedSimpleTag((SimpleTag)tag, (JspContext)pageContext, attributes);
093            }
094            else
095            {
096                throw new IllegalArgumentException("tag must be an instance of Tag or SimpleTag");
097            }
098            return nestedTag;
099        }
100        
101        /**
102         * Handles an exception that is thrown during tag lifecycle processing.
103         * Invokes <code>doCatch()</code>, if the tag implements 
104         * <code>TryCatchFinally</code>.
105         * @param tag the tag
106         * @param exc the exception to be handled
107         */
108        public static void handleException(Tag tag, Throwable exc) throws JspException
109        {
110            if(tag instanceof TryCatchFinally)
111            {
112                try
113                {
114                    ((TryCatchFinally)tag).doCatch(exc);
115                    return;
116                } 
117                catch(Throwable otherExc)
118                {
119                    exc = otherExc;
120                }
121            }
122            if(exc instanceof JspException)
123            {
124                throw ((JspException)exc);
125            }
126            if(exc instanceof RuntimeException)
127            {
128                throw ((RuntimeException)exc);
129            }
130            throw new JspException(exc);
131        }
132        
133        /**
134         * Handles the finally block of tag lifecycle processing.
135         * Invokes <code>doFinally()</code>, if the tag implements 
136         * <code>TryCatchFinally</code>.
137         * @param tag the tag
138         */
139        public static void handleFinally(Tag tag)
140        {
141            if(tag instanceof TryCatchFinally)
142            {
143                ((TryCatchFinally)tag).doFinally();
144            }
145        }
146        
147        private static void checkPageContext(Object pageContext)
148        {
149            if(pageContext instanceof PageContext) return;
150            throw new IllegalArgumentException("pageContext must be an instance of PageContext");
151        }
152        
153        private static void checkJspContext(Object pageContext)
154        {
155            if(pageContext instanceof JspContext) return;
156            throw new IllegalArgumentException("pageContext must be an instance of JspContext");
157        }
158        
159        /**
160         * Populates the specified attributes to the specified tag.
161         * @param tag the tag
162         * @param attributes the attribute map
163         */
164        public static void populateTag(Object tag, Map attributes)
165        {
166            if(null == attributes || attributes.isEmpty()) return;
167            try
168            {
169                Iterator names = attributes.keySet().iterator();
170                while(names.hasNext()) 
171                {
172                    String currentName = (String)names.next();
173                    Object currentValue = attributes.get(currentName);
174                    if(currentValue instanceof DynamicAttribute)
175                    {
176                        populateDynamicAttribute(tag, currentName, (DynamicAttribute)currentValue);
177                        return;
178                    }
179                    if(PropertyUtils.isWriteable(tag, currentName)) 
180                    {
181                        BeanUtils.copyProperty(tag, currentName, attributes.get(currentName));
182                    }
183                    else if(tag instanceof DynamicAttributes)
184                    {
185                        populateDynamicAttribute(tag, currentName, new DynamicAttribute(null, currentValue));
186                    }
187                }
188            }
189            catch(IllegalArgumentException exc)
190            {
191                throw exc;
192            }
193            catch(Exception exc)
194            {
195                log.error(exc.getMessage(), exc);
196                throw new NestedApplicationException(exc);
197            }
198        }
199        
200        private static void populateDynamicAttribute(Object tag, String name, DynamicAttribute attribute) throws JspException
201        {
202            if(!(tag instanceof DynamicAttributes))
203            {
204                String message = "Attribute " + name + " specified as dynamic attribute but tag ";
205                message += "is not an instance of " + DynamicAttributes.class.getName();
206                throw new IllegalArgumentException(message);
207            }
208            ((DynamicAttributes)tag).setDynamicAttribute(attribute.getUri(), name, attribute.getValue());
209        }
210        
211        /**
212         * Handles body evaluation of a tag. Iterated through the childs.
213         * If the child is an instance of {@link com.mockrunner.tag.NestedTag},
214         * the {@link com.mockrunner.tag.NestedTag#doLifecycle} method of
215         * this tag is called. If the child is an instance of 
216         * {@link com.mockrunner.tag.DynamicChild}, the 
217         * {@link com.mockrunner.tag.DynamicChild#evaluate} method is called
218         * and the result is written to the out <code>JspWriter</code> as a
219         * string. If the result is another object (usually a string) it is written
220         * to the out <code>JspWriter</code> (the <code>toString</code> method will
221         * be called).
222         * @param bodyList the list of body entries
223         * @param pageContext the corresponding <code>PageContext</code> or <code>JspContext</code>
224         */
225        public static void evalBody(List bodyList, Object pageContext) throws JspException
226        {
227            for(int ii = 0; ii < bodyList.size(); ii++)
228            {
229                Object nextChild = bodyList.get(ii);
230                if(nextChild instanceof NestedBodyTag)
231                {
232                    int result = ((NestedBodyTag)nextChild).doLifecycle();
233                    if(Tag.SKIP_PAGE == result) return;
234                }
235                else if(nextChild instanceof NestedStandardTag)
236                {
237                    int result = ((NestedStandardTag)nextChild).doLifecycle();
238                    if(Tag.SKIP_PAGE == result) return;
239                }
240                else if(nextChild instanceof NestedSimpleTag)
241                {
242                    ((NestedSimpleTag)nextChild).doLifecycle();
243                }
244                else
245                {
246                    try
247                    {
248                        if(pageContext instanceof PageContext)
249                        {
250                            ((PageContext)pageContext).getOut().print(getChildText(nextChild));
251                        }
252                        else if(pageContext instanceof JspContext)
253                        {
254                            ((JspContext)pageContext).getOut().print(getChildText(nextChild));
255                        }
256                        else
257                        {
258                            throw new IllegalArgumentException("pageContext must be an instance of JspContext");
259                        }
260                    }
261                    catch(IOException exc)
262                    {
263                        log.error(exc.getMessage(), exc);
264                        throw new NestedApplicationException(exc);
265                    }       
266                }
267            }
268        }
269        
270        private static String getChildText(Object child)
271        {
272            if(null == child) return "null";
273            if(child instanceof DynamicChild)
274            {
275                Object result = ((DynamicChild)child).evaluate();
276                if(null == result) return "null";
277                return result.toString();
278            }
279            return child.toString();
280        }
281        
282        /**
283         * Helper method to dump tags incl. child tags.
284         */
285        public static String dumpTag(NestedTag tag, StringBuffer buffer, int level)
286        {
287            StringUtil.appendTabs(buffer, level);
288            buffer.append("<" + tag.getClass().getName() + ">\n");
289            TagUtil.dumpTagTree(tag.getChilds(), buffer, level);
290            StringUtil.appendTabs(buffer, level);
291            buffer.append("</" + tag.getClass().getName() + ">");
292            return buffer.toString();
293        }
294        
295        /**
296         * Helper method to dump tags incl. child tags.
297         */
298        public static void dumpTagTree(List bodyList, StringBuffer buffer, int level)
299        {
300            for(int ii = 0; ii < bodyList.size(); ii++)
301            {
302                Object nextChild = bodyList.get(ii);
303                if(nextChild instanceof NestedTag)
304                {
305                    dumpTag((NestedTag)nextChild, buffer, level + 1);
306                }
307                else
308                {
309                    StringUtil.appendTabs(buffer, level + 1);
310                    buffer.append(bodyList.get(ii).toString());
311                }
312                buffer.append("\n");
313            }
314        }
315    }