1 package org.apache.commons.digester3.examples.api.catalog;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19
20 import org.apache.commons.digester3.Digester;
21
22 /**
23 * A simple program to demonstrate some of the functionality of the
24 * Commons Digester module.
25 * <p>
26 * This code will parse the provided "example.xml" file to build a tree
27 * of java objects, then cause those objects to print out their values
28 * to demonstrate that the input file has been processed correctly. The
29 * input file represents a catalog of items in a library.
30 * <p>
31 * As with all code, there are many ways of achieving the same goal;
32 * the solution here is only one possible implementation.
33 * <p>
34 * Very verbose comments are included here, as this class is intended
35 * as a tutorial; if you look closely at method "addRules", you will
36 * see that the amount of code required to use the Digester is actually
37 * quite low.
38 * <p>
39 * Usage: java Main example.xml
40 */
41 public class Main
42 {
43
44 /**
45 * Main method : entry point for running this example program.
46 * <p>
47 * Usage: java CatalogDigester example.xml
48 */
49 public static void main( String[] args )
50 {
51 if ( args.length != 1 )
52 {
53 usage();
54 System.exit( -1 );
55 }
56
57 String filename = args[0];
58
59 // Create a Digester instance
60 Digester d = new Digester();
61
62 // Add rules to the digester that will be triggered while
63 // parsing occurs.
64 addRules( d );
65
66 // Process the input file.
67 try
68 {
69 java.io.Reader reader = getInputData( filename );
70 d.parse( reader );
71 }
72 catch ( java.io.IOException ioe )
73 {
74 System.out.println( "Error reading input file:" + ioe.getMessage() );
75 System.exit( -1 );
76 }
77 catch ( org.xml.sax.SAXException se )
78 {
79 System.out.println( "Error parsing input file:" + se.getMessage() );
80 System.exit( -1 );
81 }
82
83 // Get the first object created by the digester's rules
84 // (the "root" object). Note that this is exactly the same object
85 // returned by the Digester.parse method; either approach works.
86 Catalog catalog = (Catalog) d.getRoot();
87
88 // Print out all the contents of the catalog, as loaded from
89 // the input file.
90 catalog.print();
91 }
92
93 private static void addRules( Digester d )
94 {
95
96 // --------------------------------------------------
97
98 // when we encounter the root "catalog" tag, create an
99 // 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 }