1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.struts2.dojo.views.jsp.ui;
23
24 import java.beans.IntrospectionException;
25 import java.beans.Introspector;
26 import java.beans.PropertyDescriptor;
27 import java.io.InputStream;
28 import java.lang.reflect.InvocationTargetException;
29 import java.net.URL;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.StringTokenizer;
37
38 import org.apache.struts2.ServletActionContext;
39 import org.apache.struts2.views.jsp.ui.AbstractUITag;
40
41 import com.opensymphony.xwork2.ActionContext;
42 import com.opensymphony.xwork2.util.logging.Logger;
43 import com.opensymphony.xwork2.util.logging.LoggerFactory;
44
45 /***
46 */
47 public abstract class AbstractUITagTest extends AbstractTagTest {
48
49 private static final Logger LOG = LoggerFactory.getLogger(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 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() + " in resulting String: " + writerString, 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 }