001/* $Id: RuleTestCase.java 1212599 2011-12-09 19:46:42Z 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.junit.Assert.assertEquals;
022import static org.junit.Assert.assertNotNull;
023import static org.junit.Assert.assertNull;
024import static org.junit.Assert.assertTrue;
025import static org.junit.Assert.fail;
026
027import java.io.IOException;
028import java.io.InputStream;
029import java.util.ArrayList;
030
031import org.apache.commons.digester3.Digester;
032import org.apache.commons.digester3.ExtendedBaseRules;
033import org.apache.commons.digester3.RuleSet;
034import org.junit.After;
035import org.junit.Before;
036import org.junit.Test;
037import org.xml.sax.SAXException;
038
039/**
040 * <p>
041 * Test Case for the Digester class. These tests perform parsing of XML documents to exercise the built-in rules.
042 * </p>
043 *
044 * @author Craig R. McClanahan
045 * @author Janek Bogucki
046 */
047public class RuleTestCase
048{
049
050    // ----------------------------------------------------- Instance Variables
051
052    /**
053     * The digester instance we will be processing.
054     */
055    protected Digester digester = null;
056
057    // --------------------------------------------------- Overall Test Methods
058
059    /**
060     * Set up instance variables required by this test case.
061     */
062    @Before
063    public void setUp()
064    {
065
066        digester = new Digester();
067
068    }
069
070    /**
071     * Tear down instance variables required by this test case.
072     */
073    @After
074    public void tearDown()
075    {
076
077        digester = null;
078
079    }
080
081    // ------------------------------------------------ Individual Test Methods
082
083    /**
084     * Test object creation (and associated property setting) with nothing on the stack, which should cause an
085     * appropriate Employee object to be returned.
086     */
087    @Test
088    public void testObjectCreate1()
089        throws SAXException, IOException
090    {
091
092        // Configure the digester as required
093        digester.addObjectCreate( "employee", "org.apache.commons.digester3.Employee" );
094        digester.addSetProperties( "employee" );
095
096        // Parse our test input.
097        Employee employee = digester.parse( getInputStream( "Test1.xml" ) );
098
099        assertNotNull( "Digester returned an object", employee );
100        assertEquals( "First name is correct", "First Name", employee.getFirstName() );
101        assertEquals( "Last name is correct", "Last Name", employee.getLastName() );
102
103    }
104
105    /**
106     * Test object creation (and associated property setting) with nothing on the stack, which should cause an
107     * appropriate Employee object to be returned. The processing rules will process the nested Address elements as
108     * well, but will not attempt to add them to the Employee.
109     */
110    @Test
111    public void testObjectCreate2()
112        throws SAXException, IOException
113    {
114
115        // Configure the digester as required
116        digester.addObjectCreate( "employee", Employee.class );
117        digester.addSetProperties( "employee" );
118        digester.addObjectCreate( "employee/address", "org.apache.commons.digester3.Address" );
119        digester.addSetProperties( "employee/address" );
120
121        // Parse our test input.
122        Employee employee = digester.parse( getInputStream( "Test1.xml" ) );
123
124        assertNotNull( "Digester returned an object", employee );
125        assertEquals( "First name is correct", "First Name", employee.getFirstName() );
126        assertEquals( "Last name is correct", "Last Name", employee.getLastName() );
127
128    }
129
130    /**
131     * Test object creation (and associated property setting) with nothing on the stack, which should cause an
132     * appropriate Employee object to be returned. The processing rules will process the nested Address elements as
133     * well, and will add them to the owning Employee.
134     */
135    @Test
136    public void testObjectCreate3()
137        throws SAXException, IOException
138    {
139
140        // Configure the digester as required
141        digester.addObjectCreate( "employee", Employee.class );
142        digester.addSetProperties( "employee" );
143        digester.addObjectCreate( "employee/address", "org.apache.commons.digester3.Address" );
144        digester.addSetProperties( "employee/address" );
145        digester.addSetNext( "employee/address", "addAddress" );
146
147        // Parse our test input once
148        Object root = null;
149        root = digester.parse( getInputStream( "Test1.xml" ) );
150
151        validateObjectCreate3( root );
152
153        // Parse the same input again
154        try
155        {
156            root = digester.parse( getInputStream( "Test1.xml" ) );
157        }
158        catch ( Throwable t )
159        {
160            fail( "Digester threw IOException: " + t );
161        }
162        validateObjectCreate3( root );
163
164    }
165
166    /**
167     * Same as testObjectCreate1(), except use individual call method rules to set the properties of the Employee.
168     */
169    @Test
170    public void testObjectCreate4()
171        throws SAXException, IOException
172    {
173
174        // Configure the digester as required
175        digester.addObjectCreate( "employee", Employee.class );
176        digester.addCallMethod( "employee", "setFirstName", 1 );
177        digester.addCallParam( "employee", 0, "firstName" );
178        digester.addCallMethod( "employee", "setLastName", 1 );
179        digester.addCallParam( "employee", 0, "lastName" );
180
181        // Parse our test input.
182        Employee employee = digester.parse( getInputStream( "Test1.xml" ) );
183
184        assertNotNull( "Digester returned an object", employee );
185        assertEquals( "First name is correct", "First Name", employee.getFirstName() );
186        assertEquals( "Last name is correct", "Last Name", employee.getLastName() );
187
188    }
189
190    /**
191     * Same as testObjectCreate1(), except use individual call method rules to set the properties of the Employee. Bean
192     * data are defined using elements instead of attributes. The purpose is to test CallMethod with a paramCount=0 (ie
193     * the body of the element is the argument of the method).
194     */
195    @Test
196    public void testObjectCreate5()
197        throws SAXException, IOException
198    {
199
200        // Configure the digester as required
201        digester.addObjectCreate( "employee", Employee.class );
202        digester.addCallMethod( "employee/firstName", "setFirstName", 0 );
203        digester.addCallMethod( "employee/lastName", "setLastName", 0 );
204
205        // Parse our test input.
206        Employee employee = digester.parse( getInputStream( "Test5.xml" ) );
207
208        assertNotNull( "Digester returned an object", employee );
209        assertEquals( "First name is correct", "First Name", employee.getFirstName() );
210        assertEquals( "Last name is correct", "Last Name", employee.getLastName() );
211
212    }
213
214    /**
215     * It should be possible to parse the same input twice, and get trees of objects that are isomorphic but not be
216     * identical object instances.
217     */
218    @Test
219    public void testRepeatedParse()
220        throws SAXException, IOException
221    {
222
223        // Configure the digester as required
224        digester.addObjectCreate( "employee", Employee.class );
225        digester.addSetProperties( "employee" );
226        digester.addObjectCreate( "employee/address", "org.apache.commons.digester3.Address" );
227        digester.addSetProperties( "employee/address" );
228        digester.addSetNext( "employee/address", "addAddress" );
229
230        // Parse our test input the first time
231        Object root1 = null;
232        root1 = digester.parse( getInputStream( "Test1.xml" ) );
233
234        validateObjectCreate3( root1 );
235
236        // Parse our test input the second time
237        Object root2 = null;
238        root2 = digester.parse( getInputStream( "Test1.xml" ) );
239
240        validateObjectCreate3( root2 );
241
242        // Make sure that it was a different root
243        assertTrue( "Different tree instances were returned", root1 != root2 );
244
245    }
246
247    /**
248     * Test object creation (and associated property setting) with nothing on the stack, which should cause an
249     * appropriate Employee object to be returned. The processing rules will process the nested Address elements as
250     * well, but will not attempt to add them to the Employee.
251     */
252    @Test
253    public void testRuleSet1()
254        throws SAXException, IOException
255    {
256
257        // Configure the digester as required
258        RuleSet rs = new TestRuleSet();
259        digester.addRuleSet( rs );
260
261        // Parse our test input.
262        Employee employee = digester.parse( getInputStream( "Test1.xml" ) );
263
264        assertNotNull( "Digester returned an object", employee );
265        assertEquals( "First name is correct", "First Name", employee.getFirstName() );
266        assertEquals( "Last name is correct", "Last Name", employee.getLastName() );
267        assertNotNull( "Can retrieve home address", employee.getAddress( "home" ) );
268        assertNotNull( "Can retrieve office address", employee.getAddress( "office" ) );
269
270    }
271
272    /**
273     * Same as <code>testRuleSet1</code> except using a single namespace.
274     */
275    @Test
276    public void testRuleSet2()
277        throws SAXException, IOException
278    {
279
280        // Configure the digester as required
281        digester.setNamespaceAware( true );
282        RuleSet rs = new TestRuleSet( null, "http://commons.apache.org/digester/Foo" );
283        digester.addRuleSet( rs );
284
285        // Parse our test input.
286        Employee employee = digester.parse( getInputStream( "Test2.xml" ) );
287
288        assertNotNull( "Digester returned an object", employee );
289        assertEquals( "First name is correct", "First Name", employee.getFirstName() );
290        assertEquals( "Last name is correct", "Last Name", employee.getLastName() );
291        assertNotNull( "Can retrieve home address", employee.getAddress( "home" ) );
292        assertNotNull( "Can retrieve office address", employee.getAddress( "office" ) );
293
294    }
295
296    /**
297     * Same as <code>testRuleSet2</code> except using a namespace for employee that we should recognize, and a namespace
298     * for address that we should skip.
299     */
300    @Test
301    public void testRuleSet3()
302        throws SAXException, IOException
303    {
304
305        // Configure the digester as required
306        digester.setNamespaceAware( true );
307        RuleSet rs = new TestRuleSet( null, "http://commons.apache.org/digester/Foo" );
308        digester.addRuleSet( rs );
309
310        // Parse our test input.
311        Employee employee = digester.parse( getInputStream( "Test3.xml" ) );
312
313        assertNotNull( "Digester returned an object", employee );
314        assertEquals( "First name is correct", "First Name", employee.getFirstName() );
315        assertEquals( "Last name is correct", "Last Name", employee.getLastName() );
316        assertNull( "Can not retrieve home address", employee.getAddress( "home" ) );
317        assertNull( "Can not retrieve office address", employee.getAddress( "office" ) );
318
319    }
320
321    /**
322     * Test the two argument version of the SetTopRule rule. This test is based on testObjectCreate3 and should result
323     * in the same tree of objects. Instead of using the SetNextRule rule which results in a method invocation on the
324     * (top-1) (parent) object with the top object (child) as an argument, this test uses the SetTopRule rule which
325     * results in a method invocation on the top object (child) with the top-1 (parent) object as an argument. The three
326     * argument form is tested in <code>testSetTopRule2</code>.
327     */
328    @Test
329    public void testSetTopRule1()
330        throws SAXException, IOException
331    {
332
333        // Configure the digester as required
334        digester.addObjectCreate( "employee", "org.apache.commons.digester3.Employee" );
335        digester.addSetProperties( "employee" );
336        digester.addObjectCreate( "employee/address", "org.apache.commons.digester3.Address" );
337        digester.addSetProperties( "employee/address" );
338        digester.addSetTop( "employee/address", "setEmployee" );
339
340        // Parse our test input.
341        Object root = null;
342        root = digester.parse( getInputStream( "Test1.xml" ) );
343        validateObjectCreate3( root );
344
345    }
346
347    /**
348     * Same as <code>testSetTopRule1</code> except using the three argument form of the SetTopRule rule.
349     */
350    @Test
351    public void testSetTopRule2()
352        throws SAXException, IOException
353    {
354
355        // Configure the digester as required
356        digester.addObjectCreate( "employee", "org.apache.commons.digester3.Employee" );
357        digester.addSetProperties( "employee" );
358        digester.addObjectCreate( "employee/address", "org.apache.commons.digester3.Address" );
359        digester.addSetProperties( "employee/address" );
360        digester.addSetTop( "employee/address", "setEmployee", "org.apache.commons.digester3.Employee" );
361
362        // Parse our test input.
363        Object root = null;
364        root = digester.parse( getInputStream( "Test1.xml" ) );
365
366        validateObjectCreate3( root );
367
368    }
369
370    /**
371     * Test rule addition - this boils down to making sure that digester is set properly on rule addition.
372     */
373    @Test
374    public void testAddRule()
375    {
376        Digester digester = new Digester();
377        TestRule rule = new TestRule( "Test" );
378        digester.addRule( "/root", rule );
379
380        assertEquals( "Digester is not properly on rule addition.", digester, rule.getDigester() );
381
382    }
383
384    @Test
385    public void testSetNext()
386        throws SAXException, IOException
387    {
388        Digester digester = new Digester();
389        digester.setRules( new ExtendedBaseRules() );
390        digester.setValidating( false );
391
392        digester.addObjectCreate( "!*/b", BetaBean.class );
393        digester.addObjectCreate( "!*/a", AlphaBean.class );
394        digester.addObjectCreate( "root", ArrayList.class );
395        digester.addSetProperties( "!*" );
396        digester.addSetNext( "!*/b/?", "setChild" );
397        digester.addSetNext( "!*/a/?", "setChild" );
398        digester.addSetNext( "!root/?", "add" );
399        ArrayList<?> root = digester.parse( getInputStream( "Test4.xml" ) );
400
401        assertEquals( "Wrong array size", 2, root.size() );
402        AlphaBean one = (AlphaBean) root.get( 0 );
403        assertTrue( one.getChild() instanceof BetaBean );
404        BetaBean two = (BetaBean) one.getChild();
405        assertEquals( "Wrong name (1)", two.getName(), "TWO" );
406        assertTrue( two.getChild() instanceof AlphaBean );
407        AlphaBean three = (AlphaBean) two.getChild();
408        assertEquals( "Wrong name (2)", three.getName(), "THREE" );
409        BetaBean four = (BetaBean) root.get( 1 );
410        assertEquals( "Wrong name (3)", four.getName(), "FOUR" );
411        assertTrue( four.getChild() instanceof BetaBean );
412        BetaBean five = (BetaBean) four.getChild();
413        assertEquals( "Wrong name (4)", five.getName(), "FIVE" );
414
415    }
416
417    @Test
418    public void testSetTop()
419        throws SAXException, IOException
420    {
421        Digester digester = new Digester();
422        digester.setRules( new ExtendedBaseRules() );
423        digester.setValidating( false );
424
425        digester.addObjectCreate( "!*/b", BetaBean.class );
426        digester.addObjectCreate( "!*/a", AlphaBean.class );
427        digester.addObjectCreate( "root", ArrayList.class );
428        digester.addSetProperties( "!*" );
429        digester.addSetTop( "!*/b/?", "setParent" );
430        digester.addSetTop( "!*/a/?", "setParent" );
431        digester.addSetRoot( "!*/a", "add" );
432        digester.addSetRoot( "!*/b", "add" );
433        ArrayList<?> root = digester.parse( getInputStream( "Test4.xml" ) );
434
435        assertEquals( "Wrong array size", 5, root.size() );
436
437        // note that the array is in popped order (rather than pushed)
438
439        Object obj = root.get( 1 );
440        assertTrue( "TWO should be a BetaBean", obj instanceof BetaBean );
441        BetaBean two = (BetaBean) obj;
442        assertNotNull( "Two's parent should not be null", two.getParent() );
443        assertEquals( "Wrong name (1)", "TWO", two.getName() );
444        assertEquals( "Wrong name (2)", "ONE", two.getParent().getName() );
445
446        obj = root.get( 0 );
447        assertTrue( "THREE should be an AlphaBean", obj instanceof AlphaBean );
448        AlphaBean three = (AlphaBean) obj;
449        assertNotNull( "Three's parent should not be null", three.getParent() );
450        assertEquals( "Wrong name (3)", "THREE", three.getName() );
451        assertEquals( "Wrong name (4)", "TWO", three.getParent().getName() );
452
453        obj = root.get( 3 );
454        assertTrue( "FIVE should be a BetaBean", obj instanceof BetaBean );
455        BetaBean five = (BetaBean) obj;
456        assertNotNull( "Five's parent should not be null", five.getParent() );
457        assertEquals( "Wrong name (5)", "FIVE", five.getName() );
458        assertEquals( "Wrong name (6)", "FOUR", five.getParent().getName() );
459
460    }
461
462    /**
463     */
464    @Test
465    public void testSetCustomProperties()
466        throws SAXException, IOException
467    {
468
469        Digester digester = new Digester();
470
471        digester.setValidating( false );
472
473        digester.addObjectCreate( "toplevel", ArrayList.class );
474        digester.addObjectCreate( "toplevel/one", Address.class );
475        digester.addSetNext( "toplevel/one", "add" );
476        digester.addObjectCreate( "toplevel/two", Address.class );
477        digester.addSetNext( "toplevel/two", "add" );
478        digester.addObjectCreate( "toplevel/three", Address.class );
479        digester.addSetNext( "toplevel/three", "add" );
480        digester.addObjectCreate( "toplevel/four", Address.class );
481        digester.addSetNext( "toplevel/four", "add" );
482        digester.addSetProperties( "toplevel/one" );
483        digester.addSetProperties( "toplevel/two", new String[] { "alt-street", "alt-city", "alt-state" },
484                                   new String[] { "street", "city", "state" } );
485        digester.addSetProperties( "toplevel/three", new String[] { "aCity", "state" }, new String[] { "city" } );
486        digester.addSetProperties( "toplevel/four", "alt-city", "city" );
487
488        ArrayList<?> root = digester.parse( getInputStream( "Test7.xml" ) );
489
490        assertEquals( "Wrong array size", 4, root.size() );
491
492        // note that the array is in popped order (rather than pushed)
493
494        Object obj = root.get( 0 );
495        assertTrue( "(1) Should be an Address ", obj instanceof Address );
496        Address addressOne = (Address) obj;
497        assertEquals( "(1) Street attribute", "New Street", addressOne.getStreet() );
498        assertEquals( "(1) City attribute", "Las Vegas", addressOne.getCity() );
499        assertEquals( "(1) State attribute", "Nevada", addressOne.getState() );
500
501        obj = root.get( 1 );
502        assertTrue( "(2) Should be an Address ", obj instanceof Address );
503        Address addressTwo = (Address) obj;
504        assertEquals( "(2) Street attribute", "Old Street", addressTwo.getStreet() );
505        assertEquals( "(2) City attribute", "Portland", addressTwo.getCity() );
506        assertEquals( "(2) State attribute", "Oregon", addressTwo.getState() );
507
508        obj = root.get( 2 );
509        assertTrue( "(3) Should be an Address ", obj instanceof Address );
510        Address addressThree = (Address) obj;
511        assertEquals( "(3) Street attribute", "4th Street", addressThree.getStreet() );
512        assertEquals( "(3) City attribute", "Dayton", addressThree.getCity() );
513        assertEquals( "(3) State attribute", "US", addressThree.getState() );
514
515        obj = root.get( 3 );
516        assertTrue( "(4) Should be an Address ", obj instanceof Address );
517        Address addressFour = (Address) obj;
518        assertEquals( "(4) Street attribute", "6th Street", addressFour.getStreet() );
519        assertEquals( "(4) City attribute", "Cleveland", addressFour.getCity() );
520        assertEquals( "(4) State attribute", "Ohio", addressFour.getState() );
521
522    }
523
524    // ------------------------------------------------ Utility Support Methods
525
526    /**
527     * Return an appropriate InputStream for the specified test file (which must be inside our current package.
528     *
529     * @param name Name of the test file we want
530     * @exception IOException if an input/output error occurs
531     */
532    protected InputStream getInputStream( String name )
533        throws IOException
534    {
535
536        return ( this.getClass().getResourceAsStream( "/org/apache/commons/digester3/" + name ) );
537
538    }
539
540    /**
541     * Validate the assertions for ObjectCreateRule3.
542     *
543     * @param root Root object returned by <code>digester.parse()</code>
544     */
545    protected void validateObjectCreate3( Object root )
546    {
547
548        // Validate the retrieved Employee
549        assertNotNull( "Digester returned an object", root );
550        assertTrue( "Digester returned an Employee", root instanceof Employee );
551        Employee employee = (Employee) root;
552        assertEquals( "First name is correct", "First Name", employee.getFirstName() );
553        assertEquals( "Last name is correct", "Last Name", employee.getLastName() );
554
555        // Validate the corresponding "home" Address
556        Address home = employee.getAddress( "home" );
557        assertNotNull( "Retrieved home address", home );
558        assertEquals( "Home street", "Home Street", home.getStreet() );
559        assertEquals( "Home city", "Home City", home.getCity() );
560        assertEquals( "Home state", "HS", home.getState() );
561        assertEquals( "Home zip", "HmZip", home.getZipCode() );
562
563        // Validate the corresponding "office" Address
564        Address office = employee.getAddress( "office" );
565        assertNotNull( "Retrieved office address", office );
566        assertEquals( "Office street", "Office Street", office.getStreet() );
567        assertEquals( "Office city", "Office City", office.getCity() );
568        assertEquals( "Office state", "OS", office.getState() );
569        assertEquals( "Office zip", "OfZip", office.getZipCode() );
570
571    }
572
573}