1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.jxpath.xml;
19
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.net.URL;
23 import java.util.HashMap;
24
25 import org.apache.commons.jxpath.Container;
26 import org.apache.commons.jxpath.JXPathException;
27 import org.apache.commons.jxpath.util.ClassLoaderUtil;
28
29 /**
30 * 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
31 * 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
32 * XPaths. If they are, they will be automatically read, parsed and traversed. If they are not - they won't be read at all.
33 */
34 public class DocumentContainer extends XMLParser2 implements Container {
35
36 /** DOM constant */
37 public static final String MODEL_DOM = "DOM";
38 /** JDOM constant */
39 public static final String MODEL_JDOM = "JDOM";
40 private static final long serialVersionUID = -8713290334113427066L;
41 private static HashMap<String, String> parserClasses = new HashMap<>();
42
43 static {
44 parserClasses.put(MODEL_DOM, "org.apache.commons.jxpath.xml.DOMParser");
45 parserClasses.put(MODEL_JDOM, "org.apache.commons.jxpath.xml.JDOMParser");
46 }
47
48 private static HashMap<String, XMLParser> parsers = new HashMap<>();
49
50 /**
51 * Maps a model type to a parser.
52 *
53 * @param model input model type
54 * @return XMLParser
55 */
56 private static XMLParser getParser(final String model) {
57 return parsers.computeIfAbsent(model, k -> {
58 final String className = parserClasses.get(model);
59 if (className == null) {
60 throw new JXPathException("Unsupported XML model: " + model);
61 }
62 try {
63 return ClassLoaderUtil.<XMLParser>getClass(className, true).getConstructor().newInstance();
64 } catch (final Exception ex) {
65 throw new JXPathException("Cannot allocate XMLParser: " + className, ex);
66 }
67 });
68 }
69
70 /**
71 * Add a class of a custom XML parser. Parsers for the models "DOM" and "JDOM" are pre-registered.
72 *
73 * @param model model name
74 * @param parserClassName parser class name
75 */
76 public static void registerXMLParser(final String model, final String parserClassName) {
77 parserClasses.put(model, parserClassName);
78 }
79
80 /**
81 * Add an XML parser. Parsers for the models "DOM" and "JDOM" are pre-registered.
82 *
83 * @param model model name
84 * @param parser parser
85 */
86 public static void registerXMLParser(final String model, final XMLParser parser) {
87 parsers.put(model, parser);
88 }
89
90 /**
91 * Parsed XML.
92 */
93 private Object document;
94
95 /**
96 * XML source URL.
97 */
98 private final URL xmlUrl;
99
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 }