blob: 5e942f790d39d2cb1f7eb5ef77d7729d3ad2aebe [file] [log] [blame]
package org.apache.commons.digester3.examples.api.catalog;
import java.io.IOException;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.apache.commons.digester3.Digester;
/**
* A simple program to demonstrate some of the functionality of the
* Commons Digester module.
* <p>
* This code will parse the provided "example.xml" file to build a tree
* of java objects, then cause those objects to print out their values
* to demonstrate that the input file has been processed correctly. The
* input file represents a catalog of items in a library.
* <p>
* As with all code, there are many ways of achieving the same goal;
* the solution here is only one possible implementation.
* <p>
* Very verbose comments are included here, as this class is intended
* as a tutorial; if you look closely at method "addRules", you will
* see that the amount of code required to use the Digester is actually
* quite low.
* <p>
* Usage: java Main example.xml
*/
public class Main
{
/**
* Main method : entry point for running this example program.
* <p>
* Usage: java CatalogDigester example.xml
*/
public static void main( final String[] args )
{
if ( args.length != 1 )
{
usage();
System.exit( -1 );
}
final String filename = args[0];
// Create a Digester instance
final Digester d = new Digester();
// Add rules to the digester that will be triggered while
// parsing occurs.
addRules( d );
// Process the input file.
try
{
final java.io.Reader reader = getInputData( filename );
d.parse( reader );
}
catch ( final java.io.IOException ioe )
{
System.out.println( "Error reading input file:" + ioe.getMessage() );
System.exit( -1 );
}
catch ( final org.xml.sax.SAXException se )
{
System.out.println( "Error parsing input file:" + se.getMessage() );
System.exit( -1 );
}
// Get the first object created by the digester's rules
// (the "root" object). Note that this is exactly the same object
// returned by the Digester.parse method; either approach works.
final Catalog catalog = (Catalog) d.getRoot();
// Print out all the contents of the catalog, as loaded from
// the input file.
catalog.print();
}
private static void addRules( final Digester d )
{
// --------------------------------------------------
// when we encounter the root "catalog" tag, create an
// instance of the Catalog class.
//
// Note that this approach is different from the approach taken in
// the AddressBook example, where an initial "root" object was
// explicitly created and pushed onto the digester stack before
// parsing started instead
//
// Either approach is fine.
d.addObjectCreate( "catalog", Catalog.class );
// --------------------------------------------------
// when we encounter a book tag, we want to create a Book
// instance. However the Book class doesn't have a default
// constructor (one with no arguments), so we can't use
// the ObjectCreateRule. Instead, we use the FactoryCreateRule.
final BookFactory factory = new BookFactory();
d.addFactoryCreate( "catalog/book", factory );
// and add the book to the parent catalog object (which is
// the next-to-top object on the digester object stack).
d.addSetNext( "catalog/book", "addItem" );
// we want each subtag of book to map the text contents of
// the tag into a bean property with the same name as the tag.
// eg <title>foo</title> --> setTitle("foo")
d.addSetNestedProperties( "catalog/book" );
// -----------------------------------------------
// We are using the "AudioVisual" class to represent both
// dvds and videos, so when the "dvd" tag is encountered,
// create an AudioVisual object.
d.addObjectCreate( "catalog/dvd", AudioVisual.class );
// add this dvd to the parent catalog object
d.addSetNext( "catalog/dvd", "addItem" );
// We want to map every xml attribute onto a corresponding
// property-setter method on the Dvd class instance. However
// this doesn't work with the xml attribute "year-made", because
// of the internal hyphen. We could use explicit CallMethodRule
// rules instead, or use a version of the SetPropertiesRule that
// allows us to override any troublesome mappings...
//
// If there was more than one troublesome mapping, we could
// use the method variant that takes arrays of xml-attribute-names
// and bean-property-names to override multiple mappings.
//
// For any attributes not explicitly mapped here, the default
// processing is applied, so xml attribute "category" --> setCategory.
d.addSetProperties( "catalog/dvd", "year-made", "yearMade" );
// We also need to tell this AudioVisual object that it is actually
// a dvd; we can use the ObjectParamRule to pass a string to any
// method. This usage is a little artificial - normally in this
// situation there would be separate Dvd and Video classes.
// Note also that equivalent behavior could be implemented by
// using factory objects to create & initialize the AudioVisual
// objects with their type rather than using ObjectCreateRule.
d.addCallMethod( "catalog/dvd", "setType", 1 );
d.addObjectParam( "catalog/dvd", 0, "dvd" ); // pass literal "dvd" string
// Each tag of form "<attr id="foo" value="bar"/> needs to map
// to a call to setFoo("bar").
//
// This is an alternative to the syntax used for books above (see
// method addSetNestedProperties), where the name of the subtag
// indicated which property to set. Using this syntax in the xml has
// advantages and disadvantages both for the user and the application
// developer. It is commonly used with the FactoryCreateRule variant
// which allows the target class to be created to be specified in an
// xml attribute; this feature of FactoryCreateRule is not demonstrated
// in this example, but see the Apache Tomcat configuration files for
// an example of this usage.
//
// Note that despite the name similarity, there is no link
// between SetPropertyRule and SetPropertiesRule.
d.addSetProperty( "catalog/dvd/attr", "id", "value" );
// -----------------------------------------------
// and here we repeat the dvd rules, but for the video tag.
d.addObjectCreate( "catalog/video", AudioVisual.class );
d.addSetNext( "catalog/video", "addItem" );
d.addSetProperties( "catalog/video", "year-made", "yearMade" );
d.addCallMethod( "catalog/video", "setType", 1 );
d.addObjectParam( "catalog/video", 0, "video" );
d.addSetProperty( "catalog/video/attr", "id", "value" );
}
/*
* Reads the specified file into memory, and returns a StringReader object which reads from that in-memory buffer.
* <p> This method exists just to demonstrate that the input to the digester doesn't need to be from a file; for
* example, xml could be read from a database or generated dynamically; any old buffer in memory can be processed by
* the digester. <p> Clearly, if the data is always coming from a file, then calling the Digester.parse method that
* takes a File object would be more sensible (see AddressBook example).
*/
private static java.io.Reader getInputData( final String filename )
throws IOException
{
final java.io.File srcfile = new java.io.File( filename );
final java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream( 1000 );
final byte[] buf = new byte[100];
final java.io.FileInputStream fis = new java.io.FileInputStream( srcfile );
for ( ;; )
{
final int nread = fis.read( buf );
if ( nread == -1 )
{
break;
}
baos.write( buf, 0, nread );
}
fis.close();
return new java.io.StringReader( baos.toString() );
}
private static void usage()
{
System.out.println( "Usage: java Main example.xml" );
}
}