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 }