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