View Javadoc
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 }