001/* $Id: OverlappingCallMethodRuleTestCase.java 1125723 2011-05-21 15:04:46Z simonetripodi $
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one or more
004 * contributor license agreements.  See the NOTICE file distributed with
005 * this work for additional information regarding copyright ownership.
006 * The ASF licenses this file to You under the Apache License, Version 2.0
007 * (the "License"); you may not use this file except in compliance with
008 * the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.commons.digester3;
020
021import static org.apache.commons.digester3.binder.DigesterLoader.newLoader;
022import static org.junit.Assert.assertEquals;
023
024import java.io.IOException;
025import java.io.StringReader;
026
027import org.apache.commons.digester3.binder.AbstractRulesModule;
028import org.junit.Test;
029import org.xml.sax.SAXException;
030
031/**
032 * <p>
033 * Tests for situations where CallMethodRule instances and their parameters overlap each other.
034 * </p>
035 */
036public class OverlappingCallMethodRuleTestCase
037{
038
039    // --------------------------------------------------- Overall Test Methods
040
041    String itemId;
042
043    String itemName;
044
045    public void setItemId( String id )
046    {
047        itemId = id;
048    }
049
050    public void setItemName( String name )
051    {
052        itemName = name;
053    }
054
055    // ------------------------------------------------ Individual Test Methods
056
057    @Test
058    public void testItem1()
059        throws SAXException, IOException
060    {
061        StringBuilder input = new StringBuilder();
062        input.append( "<root>" );
063        input.append( " <item id='1'>anitem</item>" );
064        input.append( "</root>" );
065
066        Digester digester = newLoader( new AbstractRulesModule()
067        {
068
069            @Override
070            protected void configure()
071            {
072                forPattern( "root/item" ).callMethod( "setItemId" ).withParamCount( 1 )
073                    .then()
074                    .callParam().fromAttribute( "id" )
075                    .then()
076                    .callMethod( "setItemName" ).withParamCount( 1 )
077                    .then()
078                    .callParam();
079            }
080
081        }).newDigester();
082
083        this.itemId = null;
084        this.itemName = null;
085        digester.push( this );
086        digester.parse( new StringReader( input.toString() ) );
087
088        assertEquals( "1", this.itemId );
089        assertEquals( "anitem", this.itemName );
090    }
091
092    @Test
093    public void testItem2()
094        throws SAXException, IOException
095    {
096        StringBuilder input = new StringBuilder();
097        input.append( "<root>" );
098        input.append( " <item id='1'>anitem</item>" );
099        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}