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}