001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.xml;
014
015import javax.xml.stream.*;
016import javax.xml.stream.util.*;
017
018import org.apache.juneau.*;
019import org.apache.juneau.annotation.*;
020import org.apache.juneau.parser.*;
021
022/**
023 * Parses text generated by the {@link XmlSerializer} class back into a POJO model.
024 *
025 * <h5 class='topic'>Media types</h5>
026 *
027 * Handles <c>Content-Type</c> types:  <bc>text/xml</bc>
028 *
029 * <h5 class='topic'>Description</h5>
030 *
031 * See the {@link XmlSerializer} class for a description of Juneau-generated XML.
032 */
033@ConfigurableContext
034public class XmlParser extends ReaderParser {
035
036   //-------------------------------------------------------------------------------------------------------------------
037   // Configurable properties
038   //-------------------------------------------------------------------------------------------------------------------
039
040   static final String PREFIX = "XmlParser";
041
042   /**
043    * Configuration property:  XML event allocator.
044    *
045    * <h5 class='section'>Property:</h5>
046    * <ul>
047    *    <li><b>Name:</b>  <js>"XmlParser.eventAllocator.c"</js>
048    *    <li><b>Data type:</b>  <code>Class&lt;? <jk>extends</jk> {@link XMLEventAllocator}&gt;</code>
049    *    <li><b>Default:</b>  <jk>null</jk>
050    *    <li><b>Session property:</b>  <jk>false</jk>
051    *    <li><b>Methods:</b>
052    *       <ul>
053    *          <li class='jm'>{@link XmlParserBuilder#eventAllocator(XMLEventAllocator)}
054    *       </ul>
055    * </ul>
056    *
057    * <h5 class='section'>Description:</h5>
058    * <p>
059    * Associates an {@link XMLEventAllocator} with this parser.
060    */
061   public static final String XML_eventAllocator = PREFIX + ".eventAllocator.c";
062
063   /**
064    * Configuration property:  Preserve root element during generalized parsing.
065    *
066    * <h5 class='section'>Property:</h5>
067    * <ul>
068    *    <li><b>Name:</b>  <js>"XmlParser.preserveRootElement.b"</js>
069    *    <li><b>Data type:</b>  <c>Boolean</c>
070    *    <li><b>Default:</b>  <jk>false</jk>
071    *    <li><b>Session property:</b>  <jk>false</jk>
072    *    <li><b>Methods:</b>
073    *       <ul>
074    *          <li class='jm'>{@link XmlParserBuilder#preserveRootElement(boolean)}
075    *          <li class='jm'>{@link XmlParserBuilder#preserveRootElement()}
076    *       </ul>
077    * </ul>
078    *
079    * <h5 class='section'>Description:</h5>
080    * <p>
081    * If <jk>true</jk>, when parsing into a generic {@link ObjectMap}, the map will contain a single entry whose key
082    * is the root element name.
083    *
084    * <h5 class='section'>Example:</h5>
085    * <p class='bcode w800'>
086    *    <jc>// Parser with preserve-root-element.</jc>
087    *    ReaderParser p1 = XmlParser
088    *       .<jsm>create</jsm>()
089    *       .preserveRootElement(<jk>true</jk>)
090    *       .build();
091    *
092    *    <jc>// Parser without preserve-root-element (the default behavior).</jc>
093    *    ReaderParser p2 = XmlParser
094    *       .<jsm>create</jsm>()
095    *       .preserveRootElement(<jk>false</jk>)
096    *       .build();
097    *
098    *    String xml = <js>"&lt;root&gt;&lt;a&gt;foobar&lt;/a&gt;&lt;/root&gt;"</js>;
099    *
100    *    <jc>// Produces:  "{ root: { a:'foobar' }}"</jc>
101    *    ObjectMap m1 = p1.parse(xml, ObjectMap.<jk>class</jk>);
102    *
103    *    <jc>// Produces:  "{ a:'foobar' }"</jc>
104    *    ObjectMap m2 = p2.parse(xml, ObjectMap.<jk>class)</jk>;
105    * </p>
106    */
107   public static final String XML_preserveRootElement = PREFIX + ".preserveRootElement.b";
108
109   /**
110    * Configuration property:  XML reporter.
111    *
112    * <h5 class='section'>Property:</h5>
113    * <ul>
114    *    <li><b>Name:</b>  <js>"XmlParser.reporter.c"</js>
115    *    <li><b>Data type:</b>  <code>Class&lt;? <jk>extends</jk> {@link XMLReporter}&gt;</code>
116    *    <li><b>Default:</b>  <jk>null</jk>
117    *    <li><b>Session property:</b>  <jk>false</jk>
118    *    <li><b>Methods:</b>
119    *       <ul>
120    *          <li class='jm'>{@link XmlParserBuilder#reporter(XMLReporter)}
121    *       </ul>
122    * </ul>
123    *
124    * <h5 class='section'>Description:</h5>
125    * <p>
126    * Associates an {@link XMLReporter} with this parser.
127    *
128    * <ul class='notes'>
129    *    <li>
130    *       Reporters are not copied to new parsers during a clone.
131    * </ul>
132    */
133   public static final String XML_reporter = PREFIX + ".reporter.c";
134
135   /**
136    * Configuration property:  XML resolver.
137    *
138    * <h5 class='section'>Property:</h5>
139    * <ul>
140    *    <li><b>Name:</b>  <js>"XmlParser.resolver.c"</js>
141    *    <li><b>Data type:</b>  <code>Class&lt;? <jk>extends</jk> {@link XMLResolver}&gt;</code>
142    *    <li><b>Default:</b>  <jk>null</jk>
143    *    <li><b>Session property:</b>  <jk>false</jk>
144    *    <li><b>Methods:</b>
145    *       <ul>
146    *          <li class='jm'>{@link XmlParserBuilder#resolver(XMLResolver)}
147    *       </ul>
148    * </ul>
149    *
150    * <h5 class='section'>Description:</h5>
151    * <p>
152    * Associates an {@link XMLResolver} with this parser.
153    */
154   public static final String XML_resolver = PREFIX + ".resolver.c";
155
156   /**
157    * Configuration property:  Enable validation.
158    *
159    * <h5 class='section'>Property:</h5>
160    * <ul>
161    *    <li><b>Name:</b>  <js>"XmlParser.validating.b"</js>
162    *    <li><b>Data type:</b>  <c>Boolean</c>
163    *    <li><b>Default:</b>  <jk>false</jk>
164    *    <li><b>Session property:</b>  <jk>false</jk>
165    *    <li><b>Methods:</b>
166    *       <ul>
167    *          <li class='jm'>{@link XmlParserBuilder#validating(boolean)}
168    *          <li class='jm'>{@link XmlParserBuilder#validating()}
169    *       </ul>
170    * </ul>
171    *
172    * <h5 class='section'>Description:</h5>
173    * <p>
174    * If <jk>true</jk>, XML document will be validated.
175    *
176    * <p>
177    * See {@link XMLInputFactory#IS_VALIDATING} for more info.
178    */
179   public static final String XML_validating = PREFIX + ".validating.b";
180
181
182   //-------------------------------------------------------------------------------------------------------------------
183   // Predefined instances
184   //-------------------------------------------------------------------------------------------------------------------
185
186   /** Default parser, all default settings.*/
187   public static final XmlParser DEFAULT = new XmlParser(PropertyStore.DEFAULT);
188
189
190   //-------------------------------------------------------------------------------------------------------------------
191   // Instance
192   //-------------------------------------------------------------------------------------------------------------------
193
194   private final boolean
195      validating,
196      preserveRootElement;
197   private final XMLReporter reporter;
198   private final XMLResolver resolver;
199   private final XMLEventAllocator eventAllocator;
200
201   /**
202    * Constructor.
203    *
204    * @param ps
205    *    The property store containing all the settings for this object.
206    */
207   public XmlParser(PropertyStore ps) {
208      this(ps, "text/xml", "application/xml");
209   }
210
211   /**
212    * Constructor.
213    *
214    * @param ps
215    *    The property store containing all the settings for this object.
216    * @param consumes
217    *    The list of media types that this parser consumes (e.g. <js>"application/json"</js>, <js>"*&#8203;/json"</js>).
218    */
219   public XmlParser(PropertyStore ps, String...consumes) {
220      super(ps, consumes);
221      validating = getBooleanProperty(XML_validating, false);
222      preserveRootElement = getBooleanProperty(XML_preserveRootElement, false);
223      reporter = getInstanceProperty(XML_reporter, XMLReporter.class, null);
224      resolver = getInstanceProperty(XML_resolver, XMLResolver.class, null);
225      eventAllocator = getInstanceProperty(XML_eventAllocator, XMLEventAllocator.class, null);
226   }
227
228   @Override /* Context */
229   public XmlParserBuilder builder() {
230      return new XmlParserBuilder(getPropertyStore());
231   }
232
233   /**
234    * Instantiates a new clean-slate {@link XmlParserBuilder} object.
235    *
236    * <p>
237    * This is equivalent to simply calling <code><jk>new</jk> XmlParserBuilder()</code>.
238    *
239    * <p>
240    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
241    * the settings of the object called on.
242    *
243    * @return A new {@link XmlParserBuilder} object.
244    */
245   public static XmlParserBuilder create() {
246      return new XmlParserBuilder();
247   }
248
249   @Override /* Parser */
250   public XmlParserSession createSession() {
251      return createSession(createDefaultSessionArgs());
252   }
253
254   @Override /* Parser */
255   public XmlParserSession createSession(ParserSessionArgs args) {
256      return new XmlParserSession(this, args);
257   }
258
259   //-----------------------------------------------------------------------------------------------------------------
260   // Properties
261   //-----------------------------------------------------------------------------------------------------------------
262
263   /**
264    * Configuration property:  XML event allocator.
265    *
266    * @see #XML_eventAllocator
267    * @return
268    *    The {@link XMLEventAllocator} associated with this parser, or <jk>null</jk> if there isn't one.
269    */
270   protected final XMLEventAllocator getEventAllocator() {
271      return eventAllocator;
272   }
273
274   /**
275    * Configuration property:  Preserve root element during generalized parsing.
276    *
277    * @see #XML_preserveRootElement
278    * @return
279    *    <jk>true</jk> if when parsing into a generic {@link ObjectMap}, the map will contain a single entry whose key
280    *    is the root element name.
281    */
282   protected final boolean isPreserveRootElement() {
283      return preserveRootElement;
284   }
285
286   /**
287    * Configuration property:  XML reporter.
288    *
289    * @see #XML_reporter
290    * @return
291    *    The {@link XMLReporter} associated with this parser, or <jk>null</jk> if there isn't one.
292    */
293   protected final XMLReporter getReporter() {
294      return reporter;
295   }
296
297   /**
298    * Configuration property:  XML resolver.
299    *
300    * @see #XML_resolver
301    * @return
302    *    The {@link XMLResolver} associated with this parser, or <jk>null</jk> if there isn't one.
303    */
304   protected final XMLResolver getResolver() {
305      return resolver;
306   }
307
308   /**
309    * Configuration property:  Enable validation.
310    *
311    * @see #XML_validating
312    * @return
313    *    <jk>true</jk> if XML document will be validated.
314    */
315   protected final boolean isValidating() {
316      return validating;
317   }
318
319   //-----------------------------------------------------------------------------------------------------------------
320   // Other methods
321   //-----------------------------------------------------------------------------------------------------------------
322
323   @Override /* Context */
324   public ObjectMap toMap() {
325      return super.toMap()
326         .append("XmlParser", new DefaultFilteringObjectMap()
327            .append("validating", validating)
328            .append("preserveRootElement", preserveRootElement)
329            .append("reporter", reporter)
330            .append("resolver", resolver)
331            .append("eventAllocator", eventAllocator)
332         );
333   }
334}