1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.struts2.views.jsp;
19
20 import java.beans.IntrospectionException;
21 import java.beans.Introspector;
22 import java.beans.PropertyDescriptor;
23 import java.io.InputStream;
24 import java.lang.reflect.InvocationTargetException;
25 import java.net.URL;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.StringTokenizer;
33
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36 import org.apache.struts2.ServletActionContext;
37 import org.apache.struts2.views.jsp.ui.AbstractUITag;
38
39 import com.opensymphony.xwork2.ActionContext;
40
41
42 /***
43 */
44 public abstract class AbstractUITagTest extends AbstractTagTest {
45
46 private static final Log LOG = LogFactory.getLog(AbstractUITagTest.class);
47
48 static final String FREEMARKER_ERROR_EXPECTATION = "Java backtrace for programmers:";
49
50 /***
51 * Simple helper class for generic tag property testing mechanism. Basically it holds a property name, a property
52 * value and an output to be expected in tag output when property was accordingly set.
53 *
54 * @author <a href="mailto:gielen@it-neering.net">Rene Gielen</a>
55 */
56 public class PropertyHolder {
57 String name, value, expectation;
58
59 public String getName() {
60 return name;
61 }
62
63 public String getValue() {
64 return value;
65 }
66
67 public String getExpectation() {
68 return expectation;
69 }
70
71 /***
72 * Construct simple holder with default expectation.
73 *
74 * @param name The property name to use.
75 * @param value The property value to set.
76 * @see #PropertyHolder(String, String, String)
77 */
78 public PropertyHolder(String name, String value) {
79 this(name, value, null);
80 }
81
82 /***
83 * Construct property holder.
84 *
85 * @param name The property name to use.
86 * @param value The property value to set.
87 * @param expectation The expected String to occur in tag output caused by setting given tag property. If
88 * <tt>null</tt>, will be set to <pre>name + "=\"" + value + "\"</pre>.
89 */
90 public PropertyHolder(String name, String value, String expectation) {
91 this.name = name;
92 this.value = value;
93 if (expectation != null) {
94 this.expectation = expectation;
95 } else {
96 this.expectation = name + "=\"" + value + "\"";
97 }
98 }
99
100 /***
101 * Convenience method for easily adding anonymous constructed instance to a given map, with {@link #getName()}
102 * as key.
103 *
104 * @param map The map to place this instance in.
105 */
106 public void addToMap(Map map) {
107 if (map != null) {
108 map.put(this.name, this);
109 }
110 }
111 }
112
113 /***
114 * Simple Helper for setting bean properties. Although BeanUtils from oscore should provide bean property setting
115 * functionality, it does not work (at least with my JDK 1.5.0_05), failing in jdk's PropertyDescriptor constructor.
116 * This implementation works safely in any case, and does not add dependency on commons-beanutils for building.
117 * TODO: Check how we can remove this crap again.
118 *
119 * @author <a href="mailto:gielen@it-neering.net">Rene Gielen</a>
120 */
121 public class BeanHelper {
122 Map propDescriptors;
123 Object bean;
124
125 public BeanHelper(Object bean) {
126 this.bean = bean;
127
128 try {
129 PropertyDescriptor[] pds;
130 pds = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
131 propDescriptors = new HashMap(pds.length + 1, 1f);
132 for (int i = 0; i < pds.length; i ++) {
133 propDescriptors.put(pds[i].getName(), pds[i]);
134 }
135 } catch (IntrospectionException e) {
136 e.printStackTrace();
137 }
138 }
139
140 public void set(String name, Object value) throws IllegalAccessException, InvocationTargetException {
141 PropertyDescriptor pd = (PropertyDescriptor) propDescriptors.get(name);
142
143 if (pd != null) {
144 pd.getWriteMethod().invoke(bean, new Object[]{value});
145 }
146 }
147
148 }
149
150 /***
151 * Initialize a map of {@link PropertyHolder} for generic tag property testing. Will be used when calling {@link
152 * #verifyGenericProperties(org.apache.struts2.views.jsp.ui.AbstractUITag, String, String[])} as properties to
153 * verify.<p/> This implementation defines testdata for all common AbstractUITag properties and may be overridden in
154 * subclasses.
155 *
156 * @return A Map of PropertyHolders values bound to {@link org.apache.struts2.views.jsp.AbstractUITagTest.PropertyHolder#getName()}
157 * as key.
158 */
159 protected Map initializedGenericTagTestProperties() {
160 Map result = new HashMap();
161 new PropertyHolder("name", "someName").addToMap(result);
162 new PropertyHolder("id", "someId").addToMap(result);
163 new PropertyHolder("cssClass", "cssClass1", "class=\"cssClass1\"").addToMap(result);
164 new PropertyHolder("cssStyle", "cssStyle1", "style=\"cssStyle1\"").addToMap(result);
165 new PropertyHolder("title", "someTitle").addToMap(result);
166 new PropertyHolder("disabled", "true", "disabled=\"disabled\"").addToMap(result);
167
168
169 new PropertyHolder("tabindex", "99").addToMap(result);
170 new PropertyHolder("value", "someValue").addToMap(result);
171 new PropertyHolder("onclick", "onclick1").addToMap(result);
172 new PropertyHolder("ondblclick", "ondblclick1").addToMap(result);
173 new PropertyHolder("onmousedown", "onmousedown1").addToMap(result);
174 new PropertyHolder("onmouseup", "onmouseup1").addToMap(result);
175 new PropertyHolder("onmouseover", "onmouseover1").addToMap(result);
176 new PropertyHolder("onmousemove", "onmousemove1").addToMap(result);
177 new PropertyHolder("onmouseout", "onmouseout1").addToMap(result);
178 new PropertyHolder("onfocus", "onfocus1").addToMap(result);
179 new PropertyHolder("onblur", "onblur1").addToMap(result);
180 new PropertyHolder("onkeypress", "onkeypress1").addToMap(result);
181 new PropertyHolder("onkeydown", "onkeydown1").addToMap(result);
182 new PropertyHolder("onkeyup", "onkeyup1").addToMap(result);
183 new PropertyHolder("onclick", "onclick1").addToMap(result);
184 new PropertyHolder("onselect", "onchange").addToMap(result);
185 return result;
186 }
187
188 /***
189 * Do a generic verification that setting certain properties on a tag causes expected output regarding this
190 * property. In most cases you would not call this directly, instead use {@link
191 * #verifyGenericProperties(org.apache.struts2.views.jsp.ui.AbstractUITag, String, String[])}.
192 *
193 * @param tag The fresh created tag instance to test.
194 * @param theme The theme to use. If <tt>null</tt>, use configured default theme.
195 * @param propertiesToTest Map of {@link PropertyHolder}s, defining properties to test.
196 * @param exclude Names of properties to exclude from particular test.
197 * @throws Exception
198 */
199 public void verifyGenericProperties(AbstractUITag tag, String theme, Map propertiesToTest, String[] exclude) throws Exception {
200 if (tag != null && propertiesToTest != null) {
201 List excludeList;
202 if (exclude != null) {
203 excludeList = Arrays.asList(exclude);
204 } else {
205 excludeList = Collections.EMPTY_LIST;
206 }
207
208 tag.setPageContext(pageContext);
209 if (theme != null) {
210 tag.setTheme(theme);
211 }
212
213 BeanHelper beanHelper = new BeanHelper(tag);
214 Iterator it = propertiesToTest.values().iterator();
215 while (it.hasNext()) {
216 PropertyHolder propertyHolder = (PropertyHolder) it.next();
217 if (! excludeList.contains(propertyHolder.getName())) {
218 beanHelper.set(propertyHolder.getName(), propertyHolder.getValue());
219 }
220 }
221 tag.doStartTag();
222 tag.doEndTag();
223 String writerString = normalize(writer.toString(), true);
224 if (LOG.isInfoEnabled()) {
225 LOG.info("AbstractUITagTest - [verifyGenericProperties]: Tag output is " + writerString);
226 }
227
228 assertTrue("Freemarker error detected in tag output: " + writerString, writerString.indexOf(FREEMARKER_ERROR_EXPECTATION) == -1);
229
230 it = propertiesToTest.values().iterator();
231 while (it.hasNext()) {
232 PropertyHolder propertyHolder = (PropertyHolder) it.next();
233 if (! excludeList.contains(propertyHolder.getName())) {
234 assertTrue("Expected to find: " + propertyHolder.getExpectation(), writerString.indexOf(propertyHolder.getExpectation()) > -1);
235 }
236 }
237 }
238 }
239
240 /***
241 * Do a generic verification that setting certain properties on a tag causes expected output regarding this
242 * property. Which properties to test with which expectations will be determined by the Map retrieved by {@link #initializedGenericTagTestProperties()}.
243 *
244 * @param tag The fresh created tag instance to test.
245 * @param theme The theme to use. If <tt>null</tt>, use configured default theme.
246 * @param exclude Names of properties to exclude from particular test.
247 * @throws Exception
248 */
249 public void verifyGenericProperties(AbstractUITag tag, String theme, String[] exclude) throws Exception {
250 verifyGenericProperties(tag, theme, initializedGenericTagTestProperties(), exclude);
251 }
252
253 /***
254 * Attempt to verify the contents of this.writer against the contents of the URL specified. verify() performs a
255 * trim on both ends
256 *
257 * @param url the HTML snippet that we want to validate against
258 * @throws Exception if the validation failed
259 */
260 public void verify(URL url) throws Exception {
261 if (url == null) {
262 fail("unable to verify a null URL");
263 } else if (this.writer == null) {
264 fail("AbstractJspWriter.writer not initialized. Unable to verify");
265 }
266
267 StringBuffer buffer = new StringBuffer(128);
268 InputStream in = url.openStream();
269 byte[] buf = new byte[4096];
270 int nbytes;
271
272 while ((nbytes = in.read(buf)) > 0) {
273 buffer.append(new String(buf, 0, nbytes));
274 }
275
276 in.close();
277
278 /***
279 * compare the trimmed values of each buffer and make sure they're equivalent. however, let's make sure to
280 * normalize the strings first to account for line termination differences between platforms.
281 */
282 String writerString = normalize(writer.toString(), true);
283 String bufferString = normalize(buffer.toString(), true);
284
285 assertEquals(bufferString, writerString);
286 }
287
288 /***
289 * Attempt to verify the contents of this.writer against the contents of the URL specified. verify() performs a
290 * trim on both ends
291 *
292 * @param url the HTML snippet that we want to validate against
293 * @throws Exception if the validation failed
294 */
295 public void verify(URL url, String[] excluded) throws Exception {
296 if (url == null) {
297 fail("unable to verify a null URL");
298 } else if (this.writer == null) {
299 fail("AbstractJspWriter.writer not initialized. Unable to verify");
300 }
301
302 StringBuffer buffer = new StringBuffer(128);
303 InputStream in = url.openStream();
304 byte[] buf = new byte[4096];
305 int nbytes;
306
307 while ((nbytes = in.read(buf)) > 0) {
308 buffer.append(new String(buf, 0, nbytes));
309 }
310
311 in.close();
312
313 /***
314 * compare the trimmed values of each buffer and make sure they're equivalent. however, let's make sure to
315 * normalize the strings first to account for line termination differences between platforms.
316 */
317 String writerString = normalize(writer.toString(), true);
318 String bufferString = normalize(buffer.toString(), true);
319
320 assertEquals(bufferString, writerString);
321 }
322
323 protected void setUp() throws Exception {
324 super.setUp();
325
326 ServletActionContext.setServletContext(pageContext.getServletContext());
327 }
328
329 protected void tearDown() throws Exception {
330 super.tearDown();
331 ActionContext.setContext(null);
332 }
333
334 /***
335 * normalizes a string so that strings generated on different platforms can be compared. any group of one or more
336 * space, tab, \r, and \n characters are converted to a single space character
337 *
338 * @param obj the object to be normalized. normalize will perform its operation on obj.toString().trim() ;
339 * @param appendSpace
340 * @return the normalized string
341 */
342 public static String normalize(Object obj, boolean appendSpace) {
343 StringTokenizer st = new StringTokenizer(obj.toString().trim(), " \t\r\n");
344 StringBuffer buffer = new StringBuffer(128);
345
346 while (st.hasMoreTokens()) {
347 buffer.append(st.nextToken());
348
349
350
351
352
353
354 }
355
356 return buffer.toString();
357 }
358 }