001package org.apache.commons.digester3.examples.api.catalog;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one or more
005 * contributor license agreements.  See the NOTICE file distributed with
006 * this work for additional information regarding copyright ownership.
007 * The ASF licenses this file to You under the Apache License, Version 2.0
008 * (the "License"); you may not use this file except in compliance with
009 * the License.  You may obtain a copy of the License at
010 * 
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 * 
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */ 
019
020import org.apache.commons.digester3.Digester;
021
022/**
023 * A simple program to demonstrate some of the functionality of the
024 * Commons Digester module.
025 * <p>
026 * This code will parse the provided "example.xml" file to build a tree
027 * of java objects, then cause those objects to print out their values
028 * to demonstrate that the input file has been processed correctly. The
029 * input file represents a catalog of items in a library.
030 * <p>
031 * As with all code, there are many ways of achieving the same goal;
032 * the solution here is only one possible implementation.
033* <p> 
034 * Very verbose comments are included here, as this class is intended
035 * as a tutorial; if you look closely at method "addRules", you will
036 * see that the amount of code required to use the Digester is actually
037 * quite low.
038 * <p>
039 * Usage: java Main example.xml
040 */
041public class Main
042{
043
044    /**
045     * Main method : entry point for running this example program.
046     * <p>
047     * Usage: java CatalogDigester example.xml
048     */
049    public static void main( String[] args )
050    {
051        if ( args.length != 1 )
052        {
053            usage();
054            System.exit( -1 );
055        }
056
057        String filename = args[0];
058
059        // Create a Digester instance
060        Digester d = new Digester();
061
062        // Add rules to the digester that will be triggered while
063        // parsing occurs.
064        addRules( d );
065
066        // Process the input file.
067        try
068        {
069            java.io.Reader reader = getInputData( filename );
070            d.parse( reader );
071        }
072        catch ( java.io.IOException ioe )
073        {
074            System.out.println( "Error reading input file:" + ioe.getMessage() );
075            System.exit( -1 );
076        }
077        catch ( org.xml.sax.SAXException se )
078        {
079            System.out.println( "Error parsing input file:" + se.getMessage() );
080            System.exit( -1 );
081        }
082
083        // Get the first object created by the digester's rules
084        // (the "root" object). Note that this is exactly the same object
085        // returned by the Digester.parse method; either approach works.
086        Catalog catalog = (Catalog) d.getRoot();
087
088        // Print out all the contents of the catalog, as loaded from
089        // the input file.
090        catalog.print();
091    }
092
093    private static void addRules( Digester d )
094    {
095
096        // --------------------------------------------------
097
098        // when we encounter the root "catalog" tag, create an
099        // instance of the Catalog class.
100        //
101        // Note that this approach is different from the approach taken in
102        // the AddressBook example, where an initial "root" object was
103        // explicitly created and pushed onto the digester stack before
104        // parsing started instead
105        //
106        // Either approach is fine.
107
108        d.addObjectCreate( "catalog", Catalog.class );
109
110        // --------------------------------------------------
111
112        // when we encounter a book tag, we want to create a Book
113        // instance. However the Book class doesn't have a default
114        // constructor (one with no arguments), so we can't use
115        // the ObjectCreateRule. Instead, we use the FactoryCreateRule.
116
117        BookFactory factory = new BookFactory();
118        d.addFactoryCreate( "catalog/book", factory );
119
120        // and add the book to the parent catalog object (which is
121        // the next-to-top object on the digester object stack).
122        d.addSetNext( "catalog/book", "addItem" );
123
124        // we want each subtag of book to map the text contents of
125        // the tag into a bean property with the same name as the tag.
126        // eg <title>foo</title> --> setTitle("foo")
127        d.addSetNestedProperties( "catalog/book" );
128
129        // -----------------------------------------------
130
131        // We are using the "AudioVisual" class to represent both
132        // dvds and videos, so when the "dvd" tag is encountered,
133        // create an AudioVisual object.
134
135        d.addObjectCreate( "catalog/dvd", AudioVisual.class );
136
137        // add this dvd to the parent catalog object
138
139        d.addSetNext( "catalog/dvd", "addItem" );
140
141        // We want to map every xml attribute onto a corresponding
142        // property-setter method on the Dvd class instance. However
143        // this doesn't work with the xml attribute "year-made", because
144        // of the internal hyphen. We could use explicit CallMethodRule
145        // rules instead, or use a version of the SetPropertiesRule that
146        // allows us to override any troublesome mappings...
147        //
148        // If there was more than one troublesome mapping, we could
149        // use the method variant that takes arrays of xml-attribute-names
150        // and bean-property-names to override multiple mappings.
151        //
152        // For any attributes not explicitly mapped here, the default
153        // processing is applied, so xml attribute "category" --> setCategory.
154
155        d.addSetProperties( "catalog/dvd", "year-made", "yearMade" );
156
157        // We also need to tell this AudioVisual object that it is actually
158        // a dvd; we can use the ObjectParamRule to pass a string to any
159        // method. This usage is a little artificial - normally in this
160        // situation there would be separate Dvd and Video classes.
161        // Note also that equivalent behaviour could be implemented by
162        // using factory objects to create & initialise the AudioVisual
163        // objects with their type rather than using ObjectCreateRule.
164
165        d.addCallMethod( "catalog/dvd", "setType", 1 );
166        d.addObjectParam( "catalog/dvd", 0, "dvd" ); // pass literal "dvd" string
167
168        // Each tag of form "<attr id="foo" value="bar"/> needs to map
169        // to a call to setFoo("bar").
170        //
171        // This is an alternative to the syntax used for books above (see
172        // method addSetNestedProperties), where the name of the subtag
173        // indicated which property to set. Using this syntax in the xml has
174        // advantages and disadvantages both for the user and the application
175        // developer. It is commonly used with the FactoryCreateRule variant
176        // which allows the target class to be created to be specified in an
177        // xml attribute; this feature of FactoryCreateRule is not demonstrated
178        // in this example, but see the Apache Tomcat configuration files for
179        // an example of this usage.
180        //
181        // Note that despite the name similarity, there is no link
182        // between SetPropertyRule and SetPropertiesRule.
183
184        d.addSetProperty( "catalog/dvd/attr", "id", "value" );
185
186        // -----------------------------------------------
187
188        // and here we repeat the dvd rules, but for the video tag.
189        d.addObjectCreate( "catalog/video", AudioVisual.class );
190        d.addSetNext( "catalog/video", "addItem" );
191        d.addSetProperties( "catalog/video", "year-made", "yearMade" );
192        d.addCallMethod( "catalog/video", "setType", 1 );
193        d.addObjectParam( "catalog/video", 0, "video" );
194        d.addSetProperty( "catalog/video/attr", "id", "value" );
195    }
196
197    /*
198     * Reads the specified file into memory, and returns a StringReader object which reads from that in-memory buffer.
199     * <p> This method exists just to demonstrate that the input to the digester doesn't need to be from a file; for
200     * example, xml could be read from a database or generated dynamically; any old buffer in memory can be processed by
201     * the digester. <p> Clearly, if the data is always coming from a file, then calling the Digester.parse method that
202     * takes a File object would be more sensible (see AddressBook example).
203     */
204    private static java.io.Reader getInputData( String filename )
205        throws java.io.IOException
206    {
207        java.io.File srcfile = new java.io.File( filename );
208
209        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream( 1000 );
210        byte[] buf = new byte[100];
211        java.io.FileInputStream fis = new java.io.FileInputStream( srcfile );
212        for ( ;; )
213        {
214            int nread = fis.read( buf );
215            if ( nread == -1 )
216            {
217                break;
218            }
219            baos.write( buf, 0, nread );
220        }
221        fis.close();
222
223        return new java.io.StringReader( baos.toString() );
224
225    }
226
227    private static void usage()
228    {
229        System.out.println( "Usage: java Main example.xml" );
230    }
231
232}