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