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.juneau.marshall.xml;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.ThrowableUtils.*;
021
022import java.io.*;
023
024import javax.xml.namespace.*;
025import javax.xml.stream.*;
026import javax.xml.stream.util.*;
027
028import org.apache.commons.xml.secure.SecureXMLInputFactory;
029import org.apache.juneau.marshall.parser.*;
030
031/**
032 * Wrapper class around a {@link XMLStreamReader}.
033 *
034 * <p>
035 * The purpose is to encapsulate the reader with the {@link ParserPipe} object so that it can be retrieved for
036 * debugging purposes.
037 *
038 * <h5 class='section'>See Also:</h5><ul>
039 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/XmlSupport">XML</a>
040 * </ul>
041 */
042@SuppressWarnings({
043   "resource" // pipe field is owned by the caller's parser session; the Reader it supplies is consumed by XMLInputFactory and not owned here.
044})
045public class XmlReader implements XMLStreamReader, Positionable {
046
047   private static volatile Boolean shareable;
048
049   private final ParserPipe pipe;
050   private final XMLStreamReader sr;
051
052   /**
053    * Constructor.
054    *
055    * @param pipe The parser input.
056    *    <br>Must not be <jk>null</jk>.
057    * @param validating The value for the {@link XMLInputFactory#IS_VALIDATING} setting.
058    * @param reporter The value for the {@link XMLInputFactory#REPORTER} setting.
059    *    <br>Can be <jk>null</jk> (the factory's default reporter is used).
060    * @param resolver The value for the {@link XMLInputFactory#RESOLVER} setting.
061    *    <br>Can be <jk>null</jk> (the factory's default resolver is used).
062    * @param eventAllocator The value for the {@link XMLInputFactory#ALLOCATOR} setting.
063    *    <br>Can be <jk>null</jk> (the factory's default event allocator is used).
064    * @throws IOException Thrown by underlying I/O.
065    * @throws XMLStreamException Thrown by underlying XML stream.
066    */
067   protected XmlReader(ParserPipe pipe, boolean validating, XMLReporter reporter, XMLResolver resolver, XMLEventAllocator eventAllocator) throws IOException, XMLStreamException {
068      this(pipe, () -> newFactory(validating, reporter, resolver, eventAllocator));
069   }
070
071   /**
072    * Constructor that obtains a configured factory from the specified supplier.
073    *
074    * <p>
075    * The supplier is called inside the same exception handling as the stream creation, so a failure to build the
076    * factory is reported exactly as a failure to create the reader.  A shared factory must not be modified.
077    *
078    * @param pipe The parser input.
079    *    <br>Must not be <jk>null</jk>.
080    * @param factory Supplies the fully-configured factory used to create the underlying stream reader.
081    *    <br>Must not be <jk>null</jk>.
082    * @throws IOException Thrown by underlying I/O.
083    * @throws XMLStreamException Thrown by underlying XML stream.
084    */
085   XmlReader(ParserPipe pipe, java.util.function.Supplier<XMLInputFactory> factory) throws IOException, XMLStreamException {
086      this.pipe = pipe;
087      try {
088         Reader r = pipe.getBufferedReader();
089         sr = factory.get().createXMLStreamReader(r);
090         sr.nextTag();
091         pipe.setPositionable(this);
092      } catch (Exception e) {
093         throw castException(IOException.class, e);
094      }
095   }
096
097   /**
098    * Creates and configures a hardened input factory.
099    *
100    * @param validating The value for the {@link XMLInputFactory#IS_VALIDATING} setting.
101    * @param reporter The value for the {@link XMLInputFactory#REPORTER} setting.  Can be <jk>null</jk>.
102    * @param resolver The value for the {@link XMLInputFactory#RESOLVER} setting.  Can be <jk>null</jk>.
103    * @param eventAllocator The value for the {@link XMLInputFactory#ALLOCATOR} setting.  Can be <jk>null</jk>.
104    * @return A new factory.
105    */
106   static XMLInputFactory newFactory(boolean validating, XMLReporter reporter, XMLResolver resolver, XMLEventAllocator eventAllocator) {
107      var factory = SecureXMLInputFactory.newInstance();
108      factory.setProperty(XMLInputFactory.IS_VALIDATING, validating);
109      factory.setProperty(XMLInputFactory.IS_COALESCING, true);
110      if (factory.isPropertySupported(XMLInputFactory.REPORTER) && nn(reporter))
111         factory.setProperty(XMLInputFactory.REPORTER, reporter);
112      if (factory.isPropertySupported(XMLInputFactory.RESOLVER) && nn(resolver))
113         factory.setProperty(XMLInputFactory.RESOLVER, resolver);
114      if (factory.isPropertySupported(XMLInputFactory.ALLOCATOR) && nn(eventAllocator))
115         factory.setProperty(XMLInputFactory.ALLOCATOR, eventAllocator);
116      return factory;
117   }
118
119   /**
120    * Returns <jk>true</jk> if the StAX implementation that {@link SecureXMLInputFactory} wraps is known to allow
121    * concurrent calls to <c>createXMLStreamReader</c> once the factory has been configured.
122    *
123    * <p>
124    * The secure factory hides the implementation behind a wrapper, so the check is made against the factory that
125    * {@link XMLInputFactory#newInstance()} returns, which is the one being wrapped.  The answer is computed once.
126    *
127    * @return <jk>true</jk> if the factory can be shared between threads.
128    */
129   static boolean isShareable() {
130      var b = shareable;
131      if (b == null)
132         shareable = b = isShareable(XMLInputFactory.newInstance());
133      return b;
134   }
135
136   /**
137    * Returns <jk>true</jk> if the specified factory is a known thread-safe implementation.
138    *
139    * <p>
140    * Only the JDK's built-in implementation and Woodstox are known to be safe; any other StAX implementation is
141    * assumed not to be and gets a fresh factory per parse.
142    *
143    * @param factory The factory to check.
144    * @return <jk>true</jk> if the factory can be shared between threads.
145    */
146   static boolean isShareable(XMLInputFactory factory) {
147      var n = factory.getClass().getName();
148      return n.startsWith("com.sun.xml.internal.stream.") || n.startsWith("com.ctc.wstx.");
149   }
150
151   @Override /* Overridden from XMLStreamReader */
152   public void close() throws XMLStreamException {
153      sr.close();
154   }
155
156   @Override /* Overridden from XMLStreamReader */
157   public int getAttributeCount() { return sr.getAttributeCount(); }
158
159   @Override /* Overridden from XMLStreamReader */
160   public String getAttributeLocalName(int index) {
161      return sr.getAttributeLocalName(index);
162   }
163
164   @Override /* Overridden from XMLStreamReader */
165   public QName getAttributeName(int index) {
166      return sr.getAttributeName(index);
167   }
168
169   @Override /* Overridden from XMLStreamReader */
170   public String getAttributeNamespace(int index) {
171      return sr.getAttributeNamespace(index);
172   }
173
174   @Override /* Overridden from XMLStreamReader */
175   public String getAttributePrefix(int index) {
176      return sr.getAttributePrefix(index);
177   }
178
179   @Override /* Overridden from XMLStreamReader */
180   public String getAttributeType(int index) {
181      return sr.getAttributeType(index);
182   }
183
184   @Override /* Overridden from XMLStreamReader */
185   public String getAttributeValue(int index) {
186      return sr.getAttributeValue(index);
187   }
188
189   @Override /* Overridden from XMLStreamReader */
190   public String getAttributeValue(String namespaceURI, String localName) {
191      return sr.getAttributeValue(namespaceURI, localName);
192   }
193
194   @Override /* Overridden from XMLStreamReader */
195   public String getCharacterEncodingScheme() { return sr.getCharacterEncodingScheme(); }
196
197   @Override /* Overridden from XMLStreamReader */
198   public String getElementText() throws XMLStreamException { return sr.getElementText(); }
199
200   @Override /* Overridden from XMLStreamReader */
201   public String getEncoding() { return sr.getEncoding(); }
202
203   @Override /* Overridden from XMLStreamReader */
204   public int getEventType() { return sr.getEventType(); }
205
206   @Override /* Overridden from XMLStreamReader */
207   public String getLocalName() { return sr.getLocalName(); }
208
209   @Override /* Overridden from XMLStreamReader */
210   public Location getLocation() { return sr.getLocation(); }
211
212   @Override /* Overridden from XMLStreamReader */
213   public QName getName() { return sr.getName(); }
214
215   @Override /* Overridden from XMLStreamReader */
216   public NamespaceContext getNamespaceContext() { return sr.getNamespaceContext(); }
217
218   @Override /* Overridden from XMLStreamReader */
219   public int getNamespaceCount() { return sr.getNamespaceCount(); }
220
221   @Override /* Overridden from XMLStreamReader */
222   public String getNamespacePrefix(int index) {
223      return sr.getNamespacePrefix(index);
224   }
225
226   @Override /* Overridden from XMLStreamReader */
227   public String getNamespaceURI() { return sr.getNamespaceURI(); }
228
229   @Override /* Overridden from XMLStreamReader */
230   public String getNamespaceURI(int index) {
231      return sr.getNamespaceURI(index);
232   }
233
234   @Override /* Overridden from XMLStreamReader */
235   public String getNamespaceURI(String prefix) {
236      return sr.getNamespaceURI(prefix);
237   }
238
239   @Override /* Overridden from XMLStreamReader */
240   public String getPIData() { return sr.getPIData(); }
241
242   /**
243    * Returns the pipe passed into the constructor.
244    *
245    * @return The pipe passed into the constructor.
246    */
247   public ParserPipe getPipe() { return pipe; }
248
249   @Override /* Overridden from XMLStreamReader */
250   public String getPITarget() { return sr.getPITarget(); }
251
252   @Override /* Overridden from Positionable */
253   public Position getPosition() {
254      var l = getLocation();
255      return new Position(l.getLineNumber(), l.getColumnNumber());
256   }
257
258   @Override /* Overridden from XMLStreamReader */
259   public String getPrefix() { return sr.getPrefix(); }
260
261   @Override /* Overridden from XMLStreamReader */
262   public Object getProperty(String name) throws IllegalArgumentException {
263      return sr.getProperty(name);
264   }
265
266   @Override /* Overridden from XMLStreamReader */
267   public String getText() { return sr.getText(); }
268
269   @Override /* Overridden from XMLStreamReader */
270   public char[] getTextCharacters() { return sr.getTextCharacters(); }
271
272   @Override /* Overridden from XMLStreamReader */
273   public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) throws XMLStreamException {
274      return sr.getTextCharacters(sourceStart, target, targetStart, length);
275   }
276
277   @Override /* Overridden from XMLStreamReader */
278   public int getTextLength() { return sr.getTextLength(); }
279
280   @Override /* Overridden from XMLStreamReader */
281   public int getTextStart() { return sr.getTextStart(); }
282
283   @Override /* Overridden from XMLStreamReader */
284   public String getVersion() { return sr.getVersion(); }
285
286   @Override /* Overridden from XMLStreamReader */
287   public boolean hasName() {
288      return sr.hasName();
289   }
290
291   @Override /* Overridden from XMLStreamReader */
292   public boolean hasNext() throws XMLStreamException {
293      return sr.hasNext();
294   }
295
296   @Override /* Overridden from XMLStreamReader */
297   public boolean hasText() {
298      return sr.hasText();
299   }
300
301   @Override /* Overridden from XMLStreamReader */
302   public boolean isAttributeSpecified(int index) {
303      return sr.isAttributeSpecified(index);
304   }
305
306   @Override /* Overridden from XMLStreamReader */
307   public boolean isCharacters() { return sr.isCharacters(); }
308
309   @Override /* Overridden from XMLStreamReader */
310   public boolean isEndElement() { return sr.isEndElement(); }
311
312   @Override /* Overridden from XMLStreamReader */
313   public boolean isStandalone() { return sr.isStandalone(); }
314
315   @Override /* Overridden from XMLStreamReader */
316   public boolean isStartElement() { return sr.isStartElement(); }
317
318   @Override /* Overridden from XMLStreamReader */
319   public boolean isWhiteSpace() { return sr.isWhiteSpace(); }
320
321   @Override /* Overridden from XMLStreamReader */
322   public int next() throws XMLStreamException {
323      return sr.next();
324   }
325
326   @Override /* Overridden from XMLStreamReader */
327   public int nextTag() throws XMLStreamException {
328      return sr.nextTag();
329   }
330
331   @Override /* Overridden from XMLStreamReader */
332   public void require(int type, String namespaceURI, String localName) throws XMLStreamException {
333      sr.require(type, namespaceURI, localName);
334   }
335
336   @Override /* Overridden from XMLStreamReader */
337   public boolean standaloneSet() {
338      return sr.standaloneSet();
339   }
340}