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 }