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}