View Javadoc

1   /* $Id: OverlappingCallMethodRuleTestCase.java 1125723 2011-05-21 15:04:46Z simonetripodi $
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one or more
4    * contributor license agreements.  See the NOTICE file distributed with
5    * this work for additional information regarding copyright ownership.
6    * The ASF licenses this file to You under the Apache License, Version 2.0
7    * (the "License"); you may not use this file except in compliance with
8    * the License.  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  
19  package org.apache.commons.digester3;
20  
21  import static org.apache.commons.digester3.binder.DigesterLoader.newLoader;
22  import static org.junit.Assert.assertEquals;
23  
24  import java.io.IOException;
25  import java.io.StringReader;
26  
27  import org.apache.commons.digester3.binder.AbstractRulesModule;
28  import org.junit.Test;
29  import org.xml.sax.SAXException;
30  
31  /**
32   * <p>
33   * Tests for situations where CallMethodRule instances and their parameters overlap each other.
34   * </p>
35   */
36  public class OverlappingCallMethodRuleTestCase
37  {
38  
39      // --------------------------------------------------- Overall Test Methods
40  
41      String itemId;
42  
43      String itemName;
44  
45      public void setItemId( String id )
46      {
47          itemId = id;
48      }
49  
50      public void setItemName( String name )
51      {
52          itemName = name;
53      }
54  
55      // ------------------------------------------------ Individual Test Methods
56  
57      @Test
58      public void testItem1()
59          throws SAXException, IOException
60      {
61          StringBuilder input = new StringBuilder();
62          input.append( "<root>" );
63          input.append( " <item id='1'>anitem</item>" );
64          input.append( "</root>" );
65  
66          Digester digester = newLoader( new AbstractRulesModule()
67          {
68  
69              @Override
70              protected void configure()
71              {
72                  forPattern( "root/item" ).callMethod( "setItemId" ).withParamCount( 1 )
73                      .then()
74                      .callParam().fromAttribute( "id" )
75                      .then()
76                      .callMethod( "setItemName" ).withParamCount( 1 )
77                      .then()
78                      .callParam();
79              }
80  
81          }).newDigester();
82  
83          this.itemId = null;
84          this.itemName = null;
85          digester.push( this );
86          digester.parse( new StringReader( input.toString() ) );
87  
88          assertEquals( "1", this.itemId );
89          assertEquals( "anitem", this.itemName );
90      }
91  
92      @Test
93      public void testItem2()
94          throws SAXException, IOException
95      {
96          StringBuilder input = new StringBuilder();
97          input.append( "<root>" );
98          input.append( " <item id='1'>anitem</item>" );
99          input.append( "</root>" );
100 
101         Digester digester = newLoader( new AbstractRulesModule()
102         {
103 
104             @Override
105             protected void configure()
106             {
107                 forPattern( "root/item" ).callMethod( "setItemId" ).withParamCount( 1 )
108                     .then()
109                     .callParam().fromAttribute( "id" )
110                     .then()
111                     .callMethod( "setItemName" ).withParamCount( 1 )
112                     .then()
113                     .callParam();
114             }
115 
116         }).newDigester();
117 
118         this.itemId = null;
119         this.itemName = null;
120         digester.push( this );
121         digester.parse( new StringReader( input.toString() ) );
122 
123         assertEquals( "1", this.itemId );
124         assertEquals( "anitem", this.itemName );
125     }
126 
127     @Test
128     public void testItem3()
129         throws SAXException, IOException
130     {
131         StringBuilder input = new StringBuilder();
132         input.append( "<root>" );
133         input.append( " <item>1</item>" );
134         input.append( "</root>" );
135 
136         Digester digester = newLoader( new AbstractRulesModule()
137         {
138 
139             @Override
140             protected void configure()
141             {
142                 forPattern( "root/item" ).callMethod( "setItemId" ).withParamCount( 1 )
143                     .then()
144                     .callParam().fromAttribute( "id" )
145                     .then()
146                     .callMethod( "setItemName" ).withParamCount( 1 )
147                     .then()
148                     .callParam();
149             }
150 
151         }).newDigester();
152 
153         digester.addCallMethod( "root/item", "setItemId", 1 );
154         digester.addCallParam( "root/item", 0 );
155         digester.addCallMethod( "root/item", "setItemName", 1 );
156         digester.addCallParam( "root/item", 0 );
157 
158         this.itemId = null;
159         this.itemName = null;
160         digester.push( this );
161         digester.parse( new StringReader( input.toString() ) );
162 
163         assertEquals( "1", this.itemId );
164         assertEquals( "1", this.itemName );
165     }
166 
167     /**
168      * This is an "anti-test" that demonstrates how digester can <i>fails</i> to produce the correct results, due to a
169      * design flaw (or at least limitation) in the way that CallMethodRule and CallParamRule work.
170      * <p>
171      * The following sequence always fails:
172      * <ul>
173      * <li>CallMethodRule A fires (pushing params array)</li>
174      * <li>CallMethodRule B fires (pushing params array)</li>
175      * <li>params rule for A fires --> writes to params of method B!</li>
176      * <li>params rule for B fires --> overwrites params for method B</li>
177      * </ul>
178      * The result is that method "b" appears to work ok, but method "a" loses its input parameters.
179      * <p>
180      * One solution is for CallParamRule objects to know which CallMethodRule they are associated with. Even this might
181      * fail in corner cases where the same rule is associated with multiple patterns, or with wildcard patterns which
182      * cause a rule to fire in a "recursive" manner. However implementing this is not possible with the current digester
183      * design.
184      */
185     @Test
186     public void testItem4()
187         throws SAXException, IOException
188     {
189         StringBuilder input = new StringBuilder();
190         input.append( "<root>" );
191         input.append( " <item>" );
192         input.append( "  <id value='1'/>" );
193         input.append( "  <name value='name'/>" );
194         input.append( " </item>" );
195         input.append( "</root>" );
196 
197         Digester digester = newLoader( new AbstractRulesModule()
198         {
199 
200             @Override
201             protected void configure()
202             {
203                 forPattern( "root/item" ).callMethod( "setItemId" ).withParamCount( 1 )
204                     .then()
205                     .callMethod( "setItemName" ).withParamCount( 1 );
206                 forPattern( "root/item/id" ).callParam().fromAttribute( "value" );
207                 forPattern( "root/item/name" ).callParam().fromAttribute( "value" );
208             }
209 
210         }).newDigester();
211 
212         this.itemId = null;
213         this.itemName = null;
214         digester.push( this );
215         digester.parse( new StringReader( input.toString() ) );
216 
217         // These are the "correct" results
218         // assertEquals("1", this.itemId);
219         // assertEquals("name", this.itemName);
220 
221         // These are what actually happens
222         assertEquals( null, this.itemId );
223         assertEquals( "name", this.itemName );
224     }
225 
226     /**
227      * This test checks that CallParamRule instances which fetch data from xml attributes work ok when invoked
228      * "recursively", ie a rule instances' methods gets called in the order
229      * begin[1]/begin[2]/body[2]/end[2]/body[1]/end[1]
230      */
231     @Test
232     public void testWildcard1()
233         throws SAXException, IOException
234     {
235         StringBuilder input = new StringBuilder();
236         input.append( "<box id='A1'>" );
237         input.append( " <box id='B1'>" );
238         input.append( "  <box id='C1'/>" );
239         input.append( "  <box id='C2'/>" );
240         input.append( " </box>" );
241         input.append( "</box>" );
242 
243         Digester digester = newLoader( new AbstractRulesModule()
244         {
245 
246             @Override
247             protected void configure()
248             {
249                 forPattern( "*/box" ).createObject().ofType( Box.class )
250                     .then()
251                     .callMethod( "setId" ).withParamCount( 1 )
252                     .then()
253                     .callParam().fromAttribute( "id" )
254                     .then()
255                     .setNext( "addChild" );
256             }
257 
258         }).newDigester();
259 
260         Box root = new Box();
261         root.setId( "root" );
262         digester.push( root );
263         digester.parse( new StringReader( input.toString() ) );
264 
265         // walk the object tree, concatenating the id strings
266         String ids = root.getIds();
267         assertEquals( "root A1 B1 C1 C2", ids );
268     }
269 
270     /**
271      * This test checks that CallParamRule instances which fetch data from the xml element body work ok when invoked
272      * "recursively", ie a rule instances' methods gets called in the order
273      * begin[1]/begin[2]/body[2]/end[2]/body[1]/end[1]
274      */
275     @Test
276     public void testWildcard2()
277         throws SAXException, IOException
278     {
279         StringBuilder input = new StringBuilder();
280         input.append( "<box>A1" );
281         input.append( " <box>B1" );
282         input.append( "  <box>C1</box>" );
283         input.append( "  <box>C2</box>" );
284         input.append( " </box>" );
285         input.append( "</box>" );
286 
287         Digester digester = newLoader( new AbstractRulesModule()
288         {
289 
290             @Override
291             protected void configure()
292             {
293                 forPattern( "*/box" ).createObject().ofType( Box.class )
294                     .then()
295                     .callMethod( "setId" ).withParamCount( 1 )
296                     .then()
297                     .callParam()
298                     .then()
299                     .setNext( "addChild" );
300             }
301 
302         }).newDigester();
303 
304         Box root = new Box();
305         root.setId( "root" );
306         digester.push( root );
307         digester.parse( new StringReader( input.toString() ) );
308 
309         // walk the object tree, concatenating the id strings
310         String ids = root.getIds();
311         assertEquals( "root A1 B1 C1 C2", ids );
312     }
313 }