001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.jxpath.xml;
018    
019    import java.io.IOException;
020    import java.io.InputStream;
021    import java.net.URL;
022    import java.util.HashMap;
023    
024    import org.apache.commons.jxpath.Container;
025    import org.apache.commons.jxpath.JXPathException;
026    
027    /**
028     * An XML document container reads and parses XML only when it is
029     * accessed.  JXPath traverses Containers transparently -
030     * you use the same paths to access objects in containers as you
031     * do to access those objects directly.  You can create
032     * XMLDocumentContainers for various XML documents that may or
033     * may not be accessed by XPaths.  If they are, they will be automatically
034     * read, parsed and traversed. If they are not - they won't be
035     * read at all.
036     *
037     * @author Dmitri Plotnikov
038     * @version $Revision: 668329 $ $Date: 2008-06-16 16:59:48 -0500 (Mon, 16 Jun 2008) $
039     */
040    public class DocumentContainer extends XMLParser2 implements Container {
041    
042        /** DOM constant */
043        public static final String MODEL_DOM = "DOM";
044    
045        /** JDOM constant */
046        public static final String MODEL_JDOM = "JDOM";
047    
048        private static final long serialVersionUID = -8713290334113427066L;
049    
050        private static HashMap parserClasses = new HashMap();
051        static {
052            parserClasses.put(MODEL_DOM,
053                              "org.apache.commons.jxpath.xml.DOMParser");
054            parserClasses.put(MODEL_JDOM,
055                              "org.apache.commons.jxpath.xml.JDOMParser");
056        }
057    
058        private static HashMap parsers = new HashMap();
059    
060        private Object document;
061        private URL xmlURL;
062        private String model;
063    
064        /**
065         * Add an XML parser.  Parsers for the models "DOM" and "JDOM" are
066         * pre-registered.
067         * @param model model name
068         * @param parser parser
069         */
070        public static void registerXMLParser(String model, XMLParser parser) {
071            parsers.put(model, parser);
072        }
073    
074        /**
075         * Add a class of a custom XML parser.
076         * Parsers for the models "DOM" and "JDOM" are pre-registered.
077         * @param model model name
078         * @param parserClassName parser classname
079         */
080        public static void registerXMLParser(String model, String parserClassName) {
081            parserClasses.put(model, parserClassName);
082        }
083    
084        /**
085         * Use this constructor if the desired model is DOM.
086         *
087         * @param xmlURL is a URL for an XML file.
088         * Use getClass().getResource(resourceName) to load XML from a
089         * resource file.
090         */
091        public DocumentContainer(URL xmlURL) {
092            this(xmlURL, MODEL_DOM);
093        }
094    
095        /**
096         * Construct a new DocumentContainer.
097         * @param xmlURL is a URL for an XML file. Use getClass().getResource
098         *               (resourceName) to load XML from a resource file.
099         *
100         * @param model is one of the MODEL_* constants defined in this class. It
101         *              determines which parser should be used to load the XML.
102         */
103        public DocumentContainer(URL xmlURL, String model) {
104            this.xmlURL = xmlURL;
105            if (xmlURL == null) {
106                throw new JXPathException("XML URL is null");
107            }
108            this.model = model;
109        }
110    
111        /**
112         * Reads XML, caches it internally and returns the Document.
113         * @return Object
114         */
115        public Object getValue() {
116            if (document == null) {
117                try {
118                    InputStream stream = null;
119                    try {
120                        if (xmlURL != null) {
121                            stream = xmlURL.openStream();
122                        }
123                        document = parseXML(stream);
124                    }
125                    finally {
126                        if (stream != null) {
127                            stream.close();
128                        }
129                    }
130                }
131                catch (IOException ex) {
132                    throw new JXPathException(
133                        "Cannot read XML from: " + xmlURL.toString(),
134                        ex);
135                }
136            }
137            return document;
138        }
139    
140        /**
141         * Parses XML using the parser for the specified model.
142         * @param stream InputStream
143         * @return Object
144         */
145        public Object parseXML(InputStream stream) {
146            XMLParser parser = getParser(model);
147            if (parser instanceof XMLParser2) {
148                XMLParser2 parser2 = (XMLParser2) parser;
149                parser2.setValidating(isValidating());
150                parser2.setNamespaceAware(isNamespaceAware());
151                parser2.setIgnoringElementContentWhitespace(
152                        isIgnoringElementContentWhitespace());
153                parser2.setExpandEntityReferences(isExpandEntityReferences());
154                parser2.setIgnoringComments(isIgnoringComments());
155                parser2.setCoalescing(isCoalescing());
156            }
157            return parser.parseXML(stream);
158        }
159    
160        /**
161         * Throws an UnsupportedOperationException.
162         * @param value value (not) to set
163         */
164        public void setValue(Object value) {
165            throw new UnsupportedOperationException();
166        }
167    
168        /**
169         * Maps a model type to a parser.
170         * @param model input model type
171         * @return XMLParser
172         */
173        private static XMLParser getParser(String model) {
174            XMLParser parser = (XMLParser) parsers.get(model);
175            if (parser == null) {
176                String className = (String) parserClasses.get(model);
177                if (className == null) {
178                    throw new JXPathException("Unsupported XML model: " + model);
179                }
180                try {
181                    Class clazz = Class.forName(className);
182                    parser = (XMLParser) clazz.newInstance();
183                }
184                catch (Exception ex) {
185                    throw new JXPathException(
186                        "Cannot allocate XMLParser: " + className, ex);
187                }
188                parsers.put(model, parser);
189            }
190            return parser;
191        }
192    }