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 */
017package org.apache.commons.jxpath.xml;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.net.URL;
022import java.util.HashMap;
023
024import org.apache.commons.jxpath.Container;
025import org.apache.commons.jxpath.JXPathException;
026import org.apache.commons.jxpath.util.ClassLoaderUtil;
027
028/**
029 * An XML document container reads and parses XML only when it is
030 * accessed.  JXPath traverses Containers transparently -
031 * you use the same paths to access objects in containers as you
032 * do to access those objects directly.  You can create
033 * XMLDocumentContainers for various XML documents that may or
034 * may not be accessed by XPaths.  If they are, they will be automatically
035 * read, parsed and traversed. If they are not - they won't be
036 * read at all.
037 *
038 * @author Dmitri Plotnikov
039 * @version $Revision: 916559 $ $Date: 2010-02-26 04:55:46 +0100 (Fr, 26 Feb 2010) $
040 */
041public class DocumentContainer extends XMLParser2 implements Container {
042
043    /** DOM constant */
044    public static final String MODEL_DOM = "DOM";
045
046    /** JDOM constant */
047    public static final String MODEL_JDOM = "JDOM";
048
049    private static final long serialVersionUID = -8713290334113427066L;
050
051    private static HashMap parserClasses = new HashMap();
052    static {
053        parserClasses.put(MODEL_DOM,
054                          "org.apache.commons.jxpath.xml.DOMParser");
055        parserClasses.put(MODEL_JDOM,
056                          "org.apache.commons.jxpath.xml.JDOMParser");
057    }
058
059    private static HashMap parsers = new HashMap();
060
061    private Object document;
062    private URL xmlURL;
063    private String model;
064
065    /**
066     * Add an XML parser.  Parsers for the models "DOM" and "JDOM" are
067     * pre-registered.
068     * @param model model name
069     * @param parser parser
070     */
071    public static void registerXMLParser(String model, XMLParser parser) {
072        parsers.put(model, parser);
073    }
074
075    /**
076     * Add a class of a custom XML parser.
077     * Parsers for the models "DOM" and "JDOM" are pre-registered.
078     * @param model model name
079     * @param parserClassName parser classname
080     */
081    public static void registerXMLParser(String model, String parserClassName) {
082        parserClasses.put(model, parserClassName);
083    }
084
085    /**
086     * Use this constructor if the desired model is DOM.
087     *
088     * @param xmlURL is a URL for an XML file.
089     * Use getClass().getResource(resourceName) to load XML from a
090     * resource file.
091     */
092    public DocumentContainer(URL xmlURL) {
093        this(xmlURL, MODEL_DOM);
094    }
095
096    /**
097     * Construct a new DocumentContainer.
098     * @param xmlURL is a URL for an XML file. Use getClass().getResource
099     *               (resourceName) to load XML from a resource file.
100     *
101     * @param model is one of the MODEL_* constants defined in this class. It
102     *              determines which parser should be used to load the XML.
103     */
104    public DocumentContainer(URL xmlURL, String model) {
105        this.xmlURL = xmlURL;
106        if (xmlURL == null) {
107            throw new JXPathException("XML URL is null");
108        }
109        this.model = model;
110    }
111
112    /**
113     * Reads XML, caches it internally and returns the Document.
114     * @return Object
115     */
116    public Object getValue() {
117        if (document == null) {
118            try {
119                InputStream stream = null;
120                try {
121                    if (xmlURL != null) {
122                        stream = xmlURL.openStream();
123                    }
124                    document = parseXML(stream);
125                }
126                finally {
127                    if (stream != null) {
128                        stream.close();
129                    }
130                }
131            }
132            catch (IOException ex) {
133                throw new JXPathException(
134                    "Cannot read XML from: " + xmlURL.toString(),
135                    ex);
136            }
137        }
138        return document;
139    }
140
141    /**
142     * Parses XML using the parser for the specified model.
143     * @param stream InputStream
144     * @return Object
145     */
146    public Object parseXML(InputStream stream) {
147        XMLParser parser = getParser(model);
148        if (parser instanceof XMLParser2) {
149            XMLParser2 parser2 = (XMLParser2) parser;
150            parser2.setValidating(isValidating());
151            parser2.setNamespaceAware(isNamespaceAware());
152            parser2.setIgnoringElementContentWhitespace(
153                    isIgnoringElementContentWhitespace());
154            parser2.setExpandEntityReferences(isExpandEntityReferences());
155            parser2.setIgnoringComments(isIgnoringComments());
156            parser2.setCoalescing(isCoalescing());
157        }
158        return parser.parseXML(stream);
159    }
160
161    /**
162     * Throws an UnsupportedOperationException.
163     * @param value value (not) to set
164     */
165    public void setValue(Object value) {
166        throw new UnsupportedOperationException();
167    }
168
169    /**
170     * Maps a model type to a parser.
171     * @param model input model type
172     * @return XMLParser
173     */
174    private static XMLParser getParser(String model) {
175        XMLParser parser = (XMLParser) parsers.get(model);
176        if (parser == null) {
177            String className = (String) parserClasses.get(model);
178            if (className == null) {
179                throw new JXPathException("Unsupported XML model: " + model);
180            }
181            try {
182                Class clazz = ClassLoaderUtil.getClass(className, true);
183                parser = (XMLParser) clazz.newInstance();
184            }
185            catch (Exception ex) {
186                throw new JXPathException(
187                    "Cannot allocate XMLParser: " + className, ex);
188            }
189            parsers.put(model, parser);
190        }
191        return parser;
192    }
193}