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 java.util.*;
016import java.util.concurrent.*;
017
018import javax.xml.stream.*;
019import javax.xml.stream.util.*;
020
021import org.apache.juneau.*;
022import org.apache.juneau.annotation.*;
023import org.apache.juneau.parser.*;
024
025/**
026 * Parses text generated by the {@link XmlSerializer} class back into a POJO model.
027 *
028 * <h5 class='topic'>Media types</h5>
029 *
030 * Handles <c>Content-Type</c> types:  <bc>text/xml</bc>
031 *
032 * <h5 class='topic'>Description</h5>
033 *
034 * See the {@link XmlSerializer} class for a description of Juneau-generated XML.
035 */
036@ConfigurableContext
037public class XmlParser extends ReaderParser implements XmlMetaProvider, XmlCommon {
038
039   //-------------------------------------------------------------------------------------------------------------------
040   // Configurable properties
041   //-------------------------------------------------------------------------------------------------------------------
042
043   static final String PREFIX = "XmlParser";
044
045   /**
046    * Configuration property:  XML event allocator.
047    *
048    * <h5 class='section'>Property:</h5>
049    * <ul class='spaced-list'>
050    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlParser#XML_eventAllocator XML_eventAllocator}
051    *    <li><b>Name:</b>  <js>"XmlParser.eventAllocator.c"</js>
052    *    <li><b>Data type:</b>  <code>Class&lt;{@link javax.xml.stream.util.XMLEventAllocator}&gt;</code>
053    *    <li><b>Default:</b>  <jk>null</jk>
054    *    <li><b>Session property:</b>  <jk>false</jk>
055    *    <li><b>Annotations:</b>
056    *       <ul>
057    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#eventAllocator()}
058    *       </ul>
059    *    <li><b>Methods:</b>
060    *       <ul>
061    *          <li class='jm'>{@link org.apache.juneau.xml.XmlParserBuilder#eventAllocator(XMLEventAllocator)}
062    *       </ul>
063    * </ul>
064    *
065    * <h5 class='section'>Description:</h5>
066    * <p>
067    * Associates an {@link XMLEventAllocator} with this parser.
068    */
069   public static final String XML_eventAllocator = PREFIX + ".eventAllocator.c";
070
071   /**
072    * Configuration property:  Preserve root element during generalized parsing.
073    *
074    * <h5 class='section'>Property:</h5>
075    * <ul class='spaced-list'>
076    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlParser#XML_preserveRootElement XML_preserveRootElement}
077    *    <li><b>Name:</b>  <js>"XmlParser.preserveRootElement.b"</js>
078    *    <li><b>Data type:</b>  <jk>boolean</jk>
079    *    <li><b>System property:</b>  <c>XmlParser.preserveRootElement</c>
080    *    <li><b>Environment variable:</b>  <c>XMLPARSER_PRESERVEROOTELEMENT</c>
081    *    <li><b>Default:</b>  <jk>false</jk>
082    *    <li><b>Session property:</b>  <jk>false</jk>
083    *    <li><b>Annotations:</b>
084    *       <ul>
085    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#preserveRootElement()}
086    *       </ul>
087    *    <li><b>Methods:</b>
088    *       <ul>
089    *          <li class='jm'>{@link org.apache.juneau.xml.XmlParserBuilder#preserveRootElement(boolean)}
090    *          <li class='jm'>{@link org.apache.juneau.xml.XmlParserBuilder#preserveRootElement()}
091    *       </ul>
092    * </ul>
093    *
094    * <h5 class='section'>Description:</h5>
095    * <p>
096    * If <jk>true</jk>, when parsing into a generic {@link ObjectMap}, the map will contain a single entry whose key
097    * is the root element name.
098    *
099    * <h5 class='section'>Example:</h5>
100    * <p class='bcode w800'>
101    *    <jc>// Parser with preserve-root-element.</jc>
102    *    ReaderParser p1 = XmlParser
103    *       .<jsm>create</jsm>()
104    *       .preserveRootElement(<jk>true</jk>)
105    *       .build();
106    *
107    *    <jc>// Parser without preserve-root-element (the default behavior).</jc>
108    *    ReaderParser p2 = XmlParser
109    *       .<jsm>create</jsm>()
110    *       .preserveRootElement(<jk>false</jk>)
111    *       .build();
112    *
113    *    String xml = <js>"&lt;root&gt;&lt;a&gt;foobar&lt;/a&gt;&lt;/root&gt;"</js>;
114    *
115    *    <jc>// Produces:  "{ root: { a:'foobar' }}"</jc>
116    *    ObjectMap m1 = p1.parse(xml, ObjectMap.<jk>class</jk>);
117    *
118    *    <jc>// Produces:  "{ a:'foobar' }"</jc>
119    *    ObjectMap m2 = p2.parse(xml, ObjectMap.<jk>class)</jk>;
120    * </p>
121    */
122   public static final String XML_preserveRootElement = PREFIX + ".preserveRootElement.b";
123
124   /**
125    * Configuration property:  XML reporter.
126    *
127    * <h5 class='section'>Property:</h5>
128    * <ul class='spaced-list'>
129    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlParser#XML_reporter XML_reporter}
130    *    <li><b>Name:</b>  <js>"XmlParser.reporter.c"</js>
131    *    <li><b>Data type:</b>  <code>Class&lt;{@link javax.xml.stream.XMLReporter}&gt;</code>
132    *    <li><b>Default:</b>  <jk>null</jk>
133    *    <li><b>Session property:</b>  <jk>false</jk>
134    *    <li><b>Annotations:</b>
135    *       <ul>
136    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#reporter()}
137    *       </ul>
138    *    <li><b>Methods:</b>
139    *       <ul>
140    *          <li class='jm'>{@link org.apache.juneau.xml.XmlParserBuilder#reporter(XMLReporter)}
141    *       </ul>
142    * </ul>
143    *
144    * <h5 class='section'>Description:</h5>
145    * <p>
146    * Associates an {@link XMLReporter} with this parser.
147    *
148    * <ul class='notes'>
149    *    <li>
150    *       Reporters are not copied to new parsers during a clone.
151    * </ul>
152    */
153   public static final String XML_reporter = PREFIX + ".reporter.c";
154
155   /**
156    * Configuration property:  XML resolver.
157    *
158    * <h5 class='section'>Property:</h5>
159    * <ul class='spaced-list'>
160    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlParser#XML_resolver XML_resolver}
161    *    <li><b>Name:</b>  <js>"XmlParser.resolver.c"</js>
162    *    <li><b>Data type:</b>  <code>Class&lt;{@link javax.xml.stream.XMLResolver}&gt;</code>
163    *    <li><b>Default:</b>  <jk>null</jk>
164    *    <li><b>Session property:</b>  <jk>false</jk>
165    *    <li><b>Annotations:</b>
166    *       <ul>
167    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#resolver()}
168    *       </ul>
169    *    <li><b>Methods:</b>
170    *       <ul>
171    *          <li class='jm'>{@link org.apache.juneau.xml.XmlParserBuilder#resolver(XMLResolver)}
172    *       </ul>
173    * </ul>
174    *
175    * <h5 class='section'>Description:</h5>
176    * <p>
177    * Associates an {@link XMLResolver} with this parser.
178    */
179   public static final String XML_resolver = PREFIX + ".resolver.c";
180
181   /**
182    * Configuration property:  Enable validation.
183    *
184    * <h5 class='section'>Property:</h5>
185    * <ul class='spaced-list'>
186    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlParser#XML_validating XML_validating}
187    *    <li><b>Name:</b>  <js>"XmlParser.validating.b"</js>
188    *    <li><b>Data type:</b>  <jk>boolean</jk>
189    *    <li><b>System property:</b>  <c>XmlParser.validating</c>
190    *    <li><b>Environment variable:</b>  <c>XMLPARSER_VALIDATING</c>
191    *    <li><b>Default:</b>  <jk>false</jk>
192    *    <li><b>Session property:</b>  <jk>false</jk>
193    *    <li><b>Annotations:</b>
194    *       <ul>
195    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#validating()}
196    *       </ul>
197    *    <li><b>Methods:</b>
198    *       <ul>
199    *          <li class='jm'>{@link org.apache.juneau.xml.XmlParserBuilder#validating(boolean)}
200    *          <li class='jm'>{@link org.apache.juneau.xml.XmlParserBuilder#validating()}
201    *       </ul>
202    * </ul>
203    *
204    * <h5 class='section'>Description:</h5>
205    * <p>
206    * If <jk>true</jk>, XML document will be validated.
207    *
208    * <p>
209    * See {@link XMLInputFactory#IS_VALIDATING} for more info.
210    */
211   public static final String XML_validating = PREFIX + ".validating.b";
212
213
214   //-------------------------------------------------------------------------------------------------------------------
215   // Predefined instances
216   //-------------------------------------------------------------------------------------------------------------------
217
218   /** Default parser, all default settings.*/
219   public static final XmlParser DEFAULT = new XmlParser(PropertyStore.DEFAULT);
220
221
222   //-------------------------------------------------------------------------------------------------------------------
223   // Instance
224   //-------------------------------------------------------------------------------------------------------------------
225
226   private final boolean
227      validating,
228      preserveRootElement;
229   private final XMLReporter reporter;
230   private final XMLResolver resolver;
231   private final XMLEventAllocator eventAllocator;
232   private final Map<ClassMeta<?>,XmlClassMeta> xmlClassMetas = new ConcurrentHashMap<>();
233   private final Map<BeanMeta<?>,XmlBeanMeta> xmlBeanMetas = new ConcurrentHashMap<>();
234   private final Map<BeanPropertyMeta,XmlBeanPropertyMeta> xmlBeanPropertyMetas = new ConcurrentHashMap<>();
235
236   /**
237    * Constructor.
238    *
239    * @param ps
240    *    The property store containing all the settings for this object.
241    */
242   public XmlParser(PropertyStore ps) {
243      this(ps, "text/xml", "application/xml");
244   }
245
246   /**
247    * Constructor.
248    *
249    * @param ps
250    *    The property store containing all the settings for this object.
251    * @param consumes
252    *    The list of media types that this parser consumes (e.g. <js>"application/json"</js>, <js>"*&#8203;/json"</js>).
253    */
254   public XmlParser(PropertyStore ps, String...consumes) {
255      super(ps, consumes);
256      validating = getBooleanProperty(XML_validating, false);
257      preserveRootElement = getBooleanProperty(XML_preserveRootElement, false);
258      reporter = getInstanceProperty(XML_reporter, XMLReporter.class, null);
259      resolver = getInstanceProperty(XML_resolver, XMLResolver.class, null);
260      eventAllocator = getInstanceProperty(XML_eventAllocator, XMLEventAllocator.class, null);
261   }
262
263   @Override /* Context */
264   public XmlParserBuilder builder() {
265      return new XmlParserBuilder(getPropertyStore());
266   }
267
268   /**
269    * Instantiates a new clean-slate {@link XmlParserBuilder} object.
270    *
271    * <p>
272    * This is equivalent to simply calling <code><jk>new</jk> XmlParserBuilder()</code>.
273    *
274    * <p>
275    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
276    * the settings of the object called on.
277    *
278    * @return A new {@link XmlParserBuilder} object.
279    */
280   public static XmlParserBuilder create() {
281      return new XmlParserBuilder();
282   }
283
284   @Override /* Parser */
285   public XmlParserSession createSession() {
286      return createSession(createDefaultSessionArgs());
287   }
288
289   @Override /* Parser */
290   public XmlParserSession createSession(ParserSessionArgs args) {
291      return new XmlParserSession(this, args);
292   }
293
294   //-----------------------------------------------------------------------------------------------------------------
295   // Extended metadata
296   //-----------------------------------------------------------------------------------------------------------------
297
298   @Override /* XmlMetaProvider */
299   public XmlClassMeta getXmlClassMeta(ClassMeta<?> cm) {
300      XmlClassMeta m = xmlClassMetas.get(cm);
301      if (m == null) {
302         m = new XmlClassMeta(cm, this);
303         xmlClassMetas.put(cm, m);
304      }
305      return m;
306   }
307
308   @Override /* XmlMetaProvider */
309   public XmlBeanMeta getXmlBeanMeta(BeanMeta<?> bm) {
310      XmlBeanMeta m = xmlBeanMetas.get(bm);
311      if (m == null) {
312         m = new XmlBeanMeta(bm, this);
313         xmlBeanMetas.put(bm, m);
314      }
315      return m;
316   }
317
318   @Override /* XmlMetaProvider */
319   public XmlBeanPropertyMeta getXmlBeanPropertyMeta(BeanPropertyMeta bpm) {
320      XmlBeanPropertyMeta m = xmlBeanPropertyMetas.get(bpm);
321      if (m == null) {
322         BeanPropertyMeta dbpm = bpm.getDelegateFor();
323         m = new XmlBeanPropertyMeta(dbpm, this);
324         xmlBeanPropertyMetas.put(bpm, m);
325      }
326      return m;
327   }
328
329   //-----------------------------------------------------------------------------------------------------------------
330   // Properties
331   //-----------------------------------------------------------------------------------------------------------------
332
333   /**
334    * Configuration property:  XML event allocator.
335    *
336    * @see #XML_eventAllocator
337    * @return
338    *    The {@link XMLEventAllocator} associated with this parser, or <jk>null</jk> if there isn't one.
339    */
340   protected final XMLEventAllocator getEventAllocator() {
341      return eventAllocator;
342   }
343
344   /**
345    * Configuration property:  Preserve root element during generalized parsing.
346    *
347    * @see #XML_preserveRootElement
348    * @return
349    *    <jk>true</jk> if when parsing into a generic {@link ObjectMap}, the map will contain a single entry whose key
350    *    is the root element name.
351    */
352   protected final boolean isPreserveRootElement() {
353      return preserveRootElement;
354   }
355
356   /**
357    * Configuration property:  XML reporter.
358    *
359    * @see #XML_reporter
360    * @return
361    *    The {@link XMLReporter} associated with this parser, or <jk>null</jk> if there isn't one.
362    */
363   protected final XMLReporter getReporter() {
364      return reporter;
365   }
366
367   /**
368    * Configuration property:  XML resolver.
369    *
370    * @see #XML_resolver
371    * @return
372    *    The {@link XMLResolver} associated with this parser, or <jk>null</jk> if there isn't one.
373    */
374   protected final XMLResolver getResolver() {
375      return resolver;
376   }
377
378   /**
379    * Configuration property:  Enable validation.
380    *
381    * @see #XML_validating
382    * @return
383    *    <jk>true</jk> if XML document will be validated.
384    */
385   protected final boolean isValidating() {
386      return validating;
387   }
388
389   //-----------------------------------------------------------------------------------------------------------------
390   // Other methods
391   //-----------------------------------------------------------------------------------------------------------------
392
393   @Override /* Context */
394   public ObjectMap toMap() {
395      return super.toMap()
396         .append("XmlParser", new DefaultFilteringObjectMap()
397            .append("validating", validating)
398            .append("preserveRootElement", preserveRootElement)
399            .append("reporter", reporter)
400            .append("resolver", resolver)
401            .append("eventAllocator", eventAllocator)
402         );
403   }
404}