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;
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 */
040public 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}