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 }