View Javadoc

1   /*
2    * $Id: AbstractUITagTest.java 651946 2008-04-27 13:41:38Z apetrelli $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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         //new PropertyHolder("label", "label", "label=\"label\"").addToMap(result);
172         //new PropertyHolder("required", "someTitle").addToMap(result);
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             if (appendSpace && st.hasMoreTokens()) {
355                 buffer.append("");
356             }
357             */
358         }
359 
360         return buffer.toString();
361     }
362 }