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