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