001    /* ========================================================================
002     * JCommon : a free general purpose class library for the Java(tm) platform
003     * ========================================================================
004     *
005     * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006     * 
007     * Project Info:  http://www.jfree.org/jcommon/index.html
008     *
009     * This library is free software; you can redistribute it and/or modify it 
010     * under the terms of the GNU Lesser General Public License as published by 
011     * the Free Software Foundation; either version 2.1 of the License, or 
012     * (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but 
015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017     * License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this library; if not, write to the Free Software
021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022     * USA.  
023     *
024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025     * in the United States and other countries.]
026     * 
027     * --------------------
028     * ReportGenerator.java
029     * --------------------
030     * (C)opyright 2002-2005, by Thomas Morgner and Contributors.
031     *
032     * Original Author:  Thomas Morgner (taquera@sherito.org);
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *
035     * $Id: ParserFrontend.java,v 1.8 2005/11/14 10:58:19 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 10-May-2002 : Initial version
040     * 12-Dec-2002 : Fixed issues reported by Checkstyle (DG);
041     * 29-Apr-2003 : Distilled from the JFreeReport project and moved into JCommon
042     *
043     */
044    
045    package org.jfree.xml;
046    
047    import java.io.BufferedInputStream;
048    import java.io.IOException;
049    import java.net.URL;
050    import javax.xml.parsers.ParserConfigurationException;
051    import javax.xml.parsers.SAXParser;
052    import javax.xml.parsers.SAXParserFactory;
053    
054    import org.jfree.util.Log;
055    import org.xml.sax.EntityResolver;
056    import org.xml.sax.InputSource;
057    import org.xml.sax.SAXException;
058    import org.xml.sax.XMLReader;
059    
060    /**
061     * The reportgenerator initializes the parser and provides an interface
062     * the the default parser.
063     *
064     * To create a report from an URL, use
065     * <code>
066     * ReportGenerator.getInstance().parseReport (URL myURl, URL contentBase);
067     * </code>
068     *
069     * @author Thomas Morgner
070     */
071    public class ParserFrontend {
072    
073        /** The report handler. */
074        private FrontendDefaultHandler defaulthandler;
075    
076        /** The parser factory. */
077        private SAXParserFactory factory;
078    
079        /** The DTD. */
080        private EntityResolver entityResolver;
081    
082        /** A flag indicating whether to use a DTD to validate the xml input. */
083        private boolean validateDTD;
084    
085        /**
086         * Creates a new report generator. The generator uses the singleton pattern by default,
087         * so use generator.getInstance() to get the generator.
088         *
089         * @param parser the parser that is used to coordinate the parsing process.
090         */
091        protected ParserFrontend(final FrontendDefaultHandler parser) {
092            if (parser == null) {
093                throw new NullPointerException();
094            }
095            this.defaulthandler = parser;
096        }
097    
098        /**
099         * Returns <code>true</code> if the report definition should be validated against the
100         * DTD, and <code>false</code> otherwise.
101         *
102         * @return A boolean.
103         */
104        public boolean isValidateDTD() {
105            return this.validateDTD;
106        }
107    
108        /**
109         * Sets a flag that controls whether or not the report definition is validated
110         * against the DTD.
111         *
112         * @param validateDTD  the flag.
113         */
114        public void setValidateDTD(final boolean validateDTD) {
115            this.validateDTD = validateDTD;
116        }
117    
118        /**
119         * Returns the entity resolver.
120         *
121         * @return The entity resolver.
122         */
123        public EntityResolver getEntityResolver() {
124            return this.entityResolver;
125        }
126    
127        /**
128         * Sets the entity resolver.
129         *
130         * @param entityResolver  the entity resolver.
131         */
132        public void setEntityResolver(final EntityResolver entityResolver) {
133            this.entityResolver = entityResolver;
134        }
135    
136        /**
137         * Returns a SAX parser.
138         *
139         * @return a SAXParser.
140         *
141         * @throws ParserConfigurationException if there is a problem configuring the parser.
142         * @throws SAXException if there is a problem with the parser initialisation
143         */
144        protected SAXParser getParser() throws ParserConfigurationException, SAXException {
145            if (this.factory == null) {
146                this.factory = SAXParserFactory.newInstance();
147                if (isValidateDTD()) {
148                    try {
149                        // dont touch the validating feature, if not needed ..
150                        this.factory.setValidating(true);
151                    }
152                    catch (Exception ex) {
153                        // the parser does not like the idea of validating ...
154                        Log.debug("The parser will not validate the xml document.", ex);
155                    }
156                }
157            }
158            return this.factory.newSAXParser();
159        }
160    
161        /**
162         * Sets the default handler used for parsing reports. This handler is used to
163         * initiate parsing.
164         *
165         * @param handler  the handler.
166         */
167        public void setDefaultHandler(final FrontendDefaultHandler handler) {
168            if (handler == null) {
169                throw new NullPointerException();
170            }
171            this.defaulthandler = handler;
172        }
173    
174        /**
175         * Returns the ElementDefinitionHandler used for parsing reports.
176         *
177         * @return the report handler.
178         */
179        public FrontendDefaultHandler getDefaultHandler() {
180            return this.defaulthandler;
181        }
182    
183        /**
184         * Creates a new instance of the currently set default handler and sets the contentbase
185         * for the handler to <code>contentBase</code>.
186         *
187         * @param contentBase  the content base.
188         *
189         * @return the report handler.
190         */
191        protected FrontendDefaultHandler createDefaultHandler(final URL contentBase) {
192            final FrontendDefaultHandler handler = getDefaultHandler().newInstance();
193            if (contentBase != null) {
194                handler.setConfigProperty(Parser.CONTENTBASE_KEY, contentBase.toExternalForm());
195            }
196            return handler;
197        }
198    
199        /**
200         * Parses an XML report template file.
201         *
202         * @param input  the input source.
203         * @param contentBase  the content base.
204         *
205         * @return the report.
206         *
207         * @throws ElementDefinitionException if an error occurred.
208         */
209        protected Object parse(final InputSource input, final URL contentBase)
210            throws ElementDefinitionException {
211            try {
212                final SAXParser parser = getParser();
213                final XMLReader reader = parser.getXMLReader();
214    
215                try {
216                    reader.setFeature("http://xml.org/sax/features/validation", isValidateDTD());
217                }
218                catch (SAXException se) {
219                    Log.debug("The XMLReader will not validate the xml document.", se);
220                }
221                final FrontendDefaultHandler handler = createDefaultHandler(contentBase);
222                configureReader(reader, handler);
223                try {
224                    reader.setContentHandler(handler);
225                    reader.setDTDHandler(handler);
226                    if (getEntityResolver() != null) {
227                        reader.setEntityResolver(getEntityResolver());
228                    }
229                    reader.setErrorHandler(handler);
230                    reader.parse(input);
231                    return handler.getResult();
232                }
233                catch (IOException e) {
234                    throw new ElementDefinitionException(e);
235                }
236            }
237            catch (ParserConfigurationException e) {
238                throw new ElementDefinitionException(e);
239            }
240            catch (SAXException e) {
241                throw new ElementDefinitionException(e);
242            }
243        }
244    
245        /**
246         * Configures the xml reader. Use this to set features or properties
247         * before the documents get parsed.
248         *
249         * @param handler the parser implementation that will handle the SAX-Callbacks.
250         * @param reader the xml reader that should be configured.
251         */
252        protected void configureReader(final XMLReader reader, final FrontendDefaultHandler handler) {
253            try {
254                reader.setProperty
255                    ("http://xml.org/sax/properties/lexical-handler", handler.getCommentHandler());
256            }
257            catch (SAXException se) {
258                Log.debug("Comments are not supported by this SAX implementation.");
259            }
260        }
261    
262        /**
263         * Parses an XML file which is loaded using the given URL. All
264         * needed relative file- and resourcespecification are loaded
265         * using the URL <code>contentBase</code> as base.
266         * <p>
267         * After the report is generated, the ReportDefinition-source and the contentbase are
268         * stored as string in the reportproperties.
269         *
270         * @param file  the URL for the report template file.
271         * @param contentBase  the URL for the report template content base.
272         *
273         * @return the parsed report.
274         *
275         * @throws IOException if an I/O error occurs.
276         * @throws ElementDefinitionException if there is a problem parsing the report template.
277         */
278        public Object parse(final URL file, final URL contentBase)
279            throws ElementDefinitionException, IOException {
280            if (file == null) {
281                throw new NullPointerException("File may not be null");
282            }
283    
284            final BufferedInputStream bin = new BufferedInputStream(file.openStream());
285            final InputSource in = new InputSource(bin);
286            in.setSystemId(file.toString());
287            final Object result = parse(in, contentBase);
288            bin.close();
289            return result;
290        }
291    
292    }