001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.util.xml;
018
019import java.util.List;
020import java.util.function.Supplier;
021
022import javax.xml.stream.XMLEventFactory;
023import javax.xml.stream.XMLEventReader;
024import javax.xml.stream.XMLEventWriter;
025import javax.xml.stream.XMLInputFactory;
026import javax.xml.stream.XMLResolver;
027import javax.xml.stream.XMLStreamException;
028import javax.xml.stream.XMLStreamReader;
029import javax.xml.stream.XMLStreamWriter;
030import javax.xml.stream.events.XMLEvent;
031import javax.xml.transform.Result;
032import javax.xml.transform.Source;
033import javax.xml.transform.stax.StAXResult;
034import javax.xml.transform.stax.StAXSource;
035
036import org.xml.sax.ContentHandler;
037import org.xml.sax.XMLReader;
038
039import org.springframework.lang.Nullable;
040import org.springframework.util.StreamUtils;
041
042/**
043 * Convenience methods for working with the StAX API. Partly historic due to JAXP 1.3
044 * compatibility; as of Spring 4.0, relying on JAXP 1.4 as included in JDK 1.6 and higher.
045 *
046 * <p>In particular, methods for using StAX ({@code javax.xml.stream}) in combination with
047 * the TrAX API ({@code javax.xml.transform}), and converting StAX readers/writers into SAX
048 * readers/handlers and vice-versa.
049 *
050 * @author Arjen Poutsma
051 * @author Juergen Hoeller
052 * @since 3.0
053 */
054public abstract class StaxUtils {
055
056        private static final XMLResolver NO_OP_XML_RESOLVER =
057                        (publicID, systemID, base, ns) -> StreamUtils.emptyInput();
058
059
060        /**
061         * Create an {@link XMLInputFactory} with Spring's defensive setup,
062         * i.e. no support for the resolution of DTDs and external entities.
063         * @return a new defensively initialized input factory instance to use
064         * @since 5.0
065         */
066        public static XMLInputFactory createDefensiveInputFactory() {
067                return createDefensiveInputFactory(XMLInputFactory::newInstance);
068        }
069
070        /**
071         * Variant of {@link #createDefensiveInputFactory()} with a custom instance.
072         * @param instanceSupplier supplier for the input factory instance
073         * @return a new defensively initialized input factory instance to use
074         * @since 5.0.12
075         */
076        public static <T extends XMLInputFactory> T createDefensiveInputFactory(Supplier<T> instanceSupplier) {
077                T inputFactory = instanceSupplier.get();
078                inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
079                inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
080                inputFactory.setXMLResolver(NO_OP_XML_RESOLVER);
081                return inputFactory;
082        }
083
084        /**
085         * Create a JAXP 1.4 {@link StAXSource} for the given {@link XMLStreamReader}.
086         * @param streamReader the StAX stream reader
087         * @return a source wrapping the {@code streamReader}
088         */
089        public static Source createStaxSource(XMLStreamReader streamReader) {
090                return new StAXSource(streamReader);
091        }
092
093        /**
094         * Create a JAXP 1.4 {@link StAXSource} for the given {@link XMLEventReader}.
095         * @param eventReader the StAX event reader
096         * @return a source wrapping the {@code eventReader}
097         */
098        public static Source createStaxSource(XMLEventReader eventReader) throws XMLStreamException {
099                return new StAXSource(eventReader);
100        }
101
102        /**
103         * Create a custom, non-JAXP 1.4 StAX {@link Source} for the given {@link XMLStreamReader}.
104         * @param streamReader the StAX stream reader
105         * @return a source wrapping the {@code streamReader}
106         */
107        public static Source createCustomStaxSource(XMLStreamReader streamReader) {
108                return new StaxSource(streamReader);
109        }
110
111        /**
112         * Create a custom, non-JAXP 1.4 StAX {@link Source} for the given {@link XMLEventReader}.
113         * @param eventReader the StAX event reader
114         * @return a source wrapping the {@code eventReader}
115         */
116        public static Source createCustomStaxSource(XMLEventReader eventReader) {
117                return new StaxSource(eventReader);
118        }
119
120        /**
121         * Indicate whether the given {@link Source} is a JAXP 1.4 StAX Source or
122         * custom StAX Source.
123         * @return {@code true} if {@code source} is a JAXP 1.4 {@link StAXSource} or
124         * custom StAX Source; {@code false} otherwise
125         */
126        public static boolean isStaxSource(Source source) {
127                return (source instanceof StAXSource || source instanceof StaxSource);
128        }
129
130        /**
131         * Return the {@link XMLStreamReader} for the given StAX Source.
132         * @param source a JAXP 1.4 {@link StAXSource}
133         * @return the {@link XMLStreamReader}
134         * @throws IllegalArgumentException if {@code source} isn't a JAXP 1.4 {@link StAXSource}
135         * or custom StAX Source
136         */
137        @Nullable
138        public static XMLStreamReader getXMLStreamReader(Source source) {
139                if (source instanceof StAXSource) {
140                        return ((StAXSource) source).getXMLStreamReader();
141                }
142                else if (source instanceof StaxSource) {
143                        return ((StaxSource) source).getXMLStreamReader();
144                }
145                else {
146                        throw new IllegalArgumentException("Source '" + source + "' is neither StaxSource nor StAXSource");
147                }
148        }
149
150        /**
151         * Return the {@link XMLEventReader} for the given StAX Source.
152         * @param source a JAXP 1.4 {@link StAXSource}
153         * @return the {@link XMLEventReader}
154         * @throws IllegalArgumentException if {@code source} isn't a JAXP 1.4 {@link StAXSource}
155         * or custom StAX Source
156         */
157        @Nullable
158        public static XMLEventReader getXMLEventReader(Source source) {
159                if (source instanceof StAXSource) {
160                        return ((StAXSource) source).getXMLEventReader();
161                }
162                else if (source instanceof StaxSource) {
163                        return ((StaxSource) source).getXMLEventReader();
164                }
165                else {
166                        throw new IllegalArgumentException("Source '" + source + "' is neither StaxSource nor StAXSource");
167                }
168        }
169
170        /**
171         * Create a JAXP 1.4 {@link StAXResult} for the given {@link XMLStreamWriter}.
172         * @param streamWriter the StAX stream writer
173         * @return a result wrapping the {@code streamWriter}
174         */
175        public static Result createStaxResult(XMLStreamWriter streamWriter) {
176                return new StAXResult(streamWriter);
177        }
178
179        /**
180         * Create a JAXP 1.4 {@link StAXResult} for the given {@link XMLEventWriter}.
181         * @param eventWriter the StAX event writer
182         * @return a result wrapping {@code streamReader}
183         */
184        public static Result createStaxResult(XMLEventWriter eventWriter) {
185                return new StAXResult(eventWriter);
186        }
187
188        /**
189         * Create a custom, non-JAXP 1.4 StAX {@link Result} for the given {@link XMLStreamWriter}.
190         * @param streamWriter the StAX stream writer
191         * @return a source wrapping the {@code streamWriter}
192         */
193        public static Result createCustomStaxResult(XMLStreamWriter streamWriter) {
194                return new StaxResult(streamWriter);
195        }
196
197        /**
198         * Create a custom, non-JAXP 1.4 StAX {@link Result} for the given {@link XMLEventWriter}.
199         * @param eventWriter the StAX event writer
200         * @return a source wrapping the {@code eventWriter}
201         */
202        public static Result createCustomStaxResult(XMLEventWriter eventWriter) {
203                return new StaxResult(eventWriter);
204        }
205
206        /**
207         * Indicate whether the given {@link Result} is a JAXP 1.4 StAX Result or
208         * custom StAX Result.
209         * @return {@code true} if {@code result} is a JAXP 1.4 {@link StAXResult} or
210         * custom StAX Result; {@code false} otherwise
211         */
212        public static boolean isStaxResult(Result result) {
213                return (result instanceof StAXResult || result instanceof StaxResult);
214        }
215
216        /**
217         * Return the {@link XMLStreamWriter} for the given StAX Result.
218         * @param result a JAXP 1.4 {@link StAXResult}
219         * @return the {@link XMLStreamReader}
220         * @throws IllegalArgumentException if {@code source} isn't a JAXP 1.4 {@link StAXResult}
221         * or custom StAX Result
222         */
223        @Nullable
224        public static XMLStreamWriter getXMLStreamWriter(Result result) {
225                if (result instanceof StAXResult) {
226                        return ((StAXResult) result).getXMLStreamWriter();
227                }
228                else if (result instanceof StaxResult) {
229                        return ((StaxResult) result).getXMLStreamWriter();
230                }
231                else {
232                        throw new IllegalArgumentException("Result '" + result + "' is neither StaxResult nor StAXResult");
233                }
234        }
235
236        /**
237         * Return the {@link XMLEventWriter} for the given StAX Result.
238         * @param result a JAXP 1.4 {@link StAXResult}
239         * @return the {@link XMLStreamReader}
240         * @throws IllegalArgumentException if {@code source} isn't a JAXP 1.4 {@link StAXResult}
241         * or custom StAX Result
242         */
243        @Nullable
244        public static XMLEventWriter getXMLEventWriter(Result result) {
245                if (result instanceof StAXResult) {
246                        return ((StAXResult) result).getXMLEventWriter();
247                }
248                else if (result instanceof StaxResult) {
249                        return ((StaxResult) result).getXMLEventWriter();
250                }
251                else {
252                        throw new IllegalArgumentException("Result '" + result + "' is neither StaxResult nor StAXResult");
253                }
254        }
255
256        /**
257         * Create a {@link XMLEventReader} from the given list of {@link XMLEvent}.
258         * @param events the list of {@link XMLEvent XMLEvents}.
259         * @return an {@code XMLEventReader} that reads from the given events
260         * @since 5.0
261         */
262        public static XMLEventReader createXMLEventReader(List<XMLEvent> events) {
263                return new ListBasedXMLEventReader(events);
264        }
265
266        /**
267         * Create a SAX {@link ContentHandler} that writes to the given StAX {@link XMLStreamWriter}.
268         * @param streamWriter the StAX stream writer
269         * @return a content handler writing to the {@code streamWriter}
270         */
271        public static ContentHandler createContentHandler(XMLStreamWriter streamWriter) {
272                return new StaxStreamHandler(streamWriter);
273        }
274
275        /**
276         * Create a SAX {@link ContentHandler} that writes events to the given StAX {@link XMLEventWriter}.
277         * @param eventWriter the StAX event writer
278         * @return a content handler writing to the {@code eventWriter}
279         */
280        public static ContentHandler createContentHandler(XMLEventWriter eventWriter) {
281                return new StaxEventHandler(eventWriter);
282        }
283
284        /**
285         * Create a SAX {@link XMLReader} that reads from the given StAX {@link XMLStreamReader}.
286         * @param streamReader the StAX stream reader
287         * @return a XMLReader reading from the {@code streamWriter}
288         */
289        public static XMLReader createXMLReader(XMLStreamReader streamReader) {
290                return new StaxStreamXMLReader(streamReader);
291        }
292
293        /**
294         * Create a SAX {@link XMLReader} that reads from the given StAX {@link XMLEventReader}.
295         * @param eventReader the StAX event reader
296         * @return a XMLReader reading from the {@code eventWriter}
297         */
298        public static XMLReader createXMLReader(XMLEventReader eventReader) {
299                return new StaxEventXMLReader(eventReader);
300        }
301
302        /**
303         * Return a {@link XMLStreamReader} that reads from a {@link XMLEventReader}.
304         * Useful because the StAX {@code XMLInputFactory} allows one to create an
305         * event reader from a stream reader, but not vice-versa.
306         * @return a stream reader that reads from an event reader
307         */
308        public static XMLStreamReader createEventStreamReader(XMLEventReader eventReader) throws XMLStreamException {
309                return new XMLEventStreamReader(eventReader);
310        }
311
312        /**
313         * Return a {@link XMLStreamWriter} that writes to a {@link XMLEventWriter}.
314         * @return a stream writer that writes to an event writer
315         * @since 3.2
316         */
317        public static XMLStreamWriter createEventStreamWriter(XMLEventWriter eventWriter) {
318                return new XMLEventStreamWriter(eventWriter, XMLEventFactory.newFactory());
319        }
320
321        /**
322         * Return a {@link XMLStreamWriter} that writes to a {@link XMLEventWriter}.
323         * @return a stream writer that writes to an event writer
324         * @since 3.0.5
325         */
326        public static XMLStreamWriter createEventStreamWriter(XMLEventWriter eventWriter, XMLEventFactory eventFactory) {
327                return new XMLEventStreamWriter(eventWriter, eventFactory);
328        }
329
330}