View Javadoc

1   /*
2    * $Id: AbstractUITagTest.java 439747 2006-09-03 09:22:46Z mrdon $
3    *
4    * Copyright 2006 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
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         //new PropertyHolder("label", "label", "label=\"label\"").addToMap(result);
168         //new PropertyHolder("required", "someTitle").addToMap(result);
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             if (appendSpace && st.hasMoreTokens()) {
351                 buffer.append("");
352             }
353             */
354         }
355 
356         return buffer.toString();
357     }
358 }