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.collections.*;
024import org.apache.juneau.parser.*;
025
026/**
027 * Parses text generated by the {@link XmlSerializer} class back into a POJO model.
028 *
029 * <h5 class='topic'>Media types</h5>
030 *
031 * Handles <c>Content-Type</c> types:  <bc>text/xml</bc>
032 *
033 * <h5 class='topic'>Description</h5>
034 *
035 * See the {@link XmlSerializer} class for a description of Juneau-generated XML.
036 */
037@ConfigurableContext
038public class XmlParser extends ReaderParser implements XmlMetaProvider, XmlCommon {
039
040   //-------------------------------------------------------------------------------------------------------------------
041   // Configurable properties
042   //-------------------------------------------------------------------------------------------------------------------
043
044   static final String PREFIX = "XmlParser";
045
046   /**
047    * Configuration property:  XML event allocator.
048    *
049    * <h5 class='section'>Property:</h5>
050    * <ul class='spaced-list'>
051    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlParser#XML_eventAllocator XML_eventAllocator}
052    *    <li><b>Name:</b>  <js>"XmlParser.eventAllocator.c"</js>
053    *    <li><b>Data type:</b>  <code>Class&lt;{@link javax.xml.stream.util.XMLEventAllocator}&gt;</code>
054    *    <li><b>Default:</b>  <jk>null</jk>
055    *    <li><b>Session property:</b>  <jk>false</jk>
056    *    <li><b>Annotations:</b>
057    *       <ul>
058    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#eventAllocator()}
059    *       </ul>
060    *    <li><b>Methods:</b>
061    *       <ul>
062    *          <li class='jm'>{@link org.apache.juneau.xml.XmlParserBuilder#eventAllocator(XMLEventAllocator)}
063    *       </ul>
064    * </ul>
065    *
066    * <h5 class='section'>Description:</h5>
067    * <p>
068    * Associates an {@link XMLEventAllocator} with this parser.
069    */
070   public static final String XML_eventAllocator = PREFIX + ".eventAllocator.c";
071
072   /**
073    * Configuration property:  Preserve root element during generalized parsing.
074    *
075    * <h5 class='section'>Property:</h5>
076    * <ul class='spaced-list'>
077    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlParser#XML_preserveRootElement XML_preserveRootElement}
078    *    <li><b>Name:</b>  <js>"XmlParser.preserveRootElement.b"</js>
079    *    <li><b>Data type:</b>  <jk>boolean</jk>
080    *    <li><b>System property:</b>  <c>XmlParser.preserveRootElement</c>
081    *    <li><b>Environment variable:</b>  <c>XMLPARSER_PRESERVEROOTELEMENT</c>
082    *    <li><b>Default:</b>  <jk>false</jk>
083    *    <li><b>Session property:</b>  <jk>false</jk>
084    *    <li><b>Annotations:</b>
085    *       <ul>
086    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#preserveRootElement()}
087    *       </ul>
088    *    <li><b>Methods:</b>
089    *       <ul>
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 OMap}, 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    *    OMap m1 = p1.parse(xml, OMap.<jk>class</jk>);
117    *
118    *    <jc>// Produces:  "{ a:'foobar' }"</jc>
119    *    OMap m2 = p2.parse(xml, OMap.<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()}
200    *       </ul>
201    * </ul>
202    *
203    * <h5 class='section'>Description:</h5>
204    * <p>
205    * If <jk>true</jk>, XML document will be validated.
206    *
207    * <p>
208    * See {@link XMLInputFactory#IS_VALIDATING} for more info.
209    */
210   public static final String XML_validating = PREFIX + ".validating.b";
211
212
213   //-------------------------------------------------------------------------------------------------------------------
214   // Predefined instances
215   //-------------------------------------------------------------------------------------------------------------------
216
217   /** Default parser, all default settings.*/
218   public static final XmlParser DEFAULT = new XmlParser(PropertyStore.DEFAULT);
219
220
221   //-------------------------------------------------------------------------------------------------------------------
222   // Instance
223   //-------------------------------------------------------------------------------------------------------------------
224
225   private final boolean
226      validating,
227      preserveRootElement;
228   private final XMLReporter reporter;
229   private final XMLResolver resolver;
230   private final XMLEventAllocator eventAllocator;
231   private final Map<ClassMeta<?>,XmlClassMeta> xmlClassMetas = new ConcurrentHashMap<>();
232   private final Map<BeanMeta<?>,XmlBeanMeta> xmlBeanMetas = new ConcurrentHashMap<>();
233   private final Map<BeanPropertyMeta,XmlBeanPropertyMeta> xmlBeanPropertyMetas = new ConcurrentHashMap<>();
234
235   /**
236    * Constructor.
237    *
238    * @param ps
239    *    The property store containing all the settings for this object.
240    */
241   public XmlParser(PropertyStore ps) {
242      this(ps, "text/xml", "application/xml");
243   }
244
245   /**
246    * Constructor.
247    *
248    * @param ps
249    *    The property store containing all the settings for this object.
250    * @param consumes
251    *    The list of media types that this parser consumes (e.g. <js>"application/json"</js>, <js>"*&#8203;/json"</js>).
252    */
253   public XmlParser(PropertyStore ps, String...consumes) {
254      super(ps, consumes);
255      validating = getBooleanProperty(XML_validating, false);
256      preserveRootElement = getBooleanProperty(XML_preserveRootElement, false);
257      reporter = getInstanceProperty(XML_reporter, XMLReporter.class, null);
258      resolver = getInstanceProperty(XML_resolver, XMLResolver.class, null);
259      eventAllocator = getInstanceProperty(XML_eventAllocator, XMLEventAllocator.class, null);
260   }
261
262   @Override /* Context */
263   public XmlParserBuilder builder() {
264      return new XmlParserBuilder(getPropertyStore());
265   }
266
267   /**
268    * Instantiates a new clean-slate {@link XmlParserBuilder} object.
269    *
270    * <p>
271    * This is equivalent to simply calling <code><jk>new</jk> XmlParserBuilder()</code>.
272    *
273    * <p>
274    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
275    * the settings of the object called on.
276    *
277    * @return A new {@link XmlParserBuilder} object.
278    */
279   public static XmlParserBuilder create() {
280      return new XmlParserBuilder();
281   }
282
283   @Override /* Parser */
284   public XmlParserSession createSession() {
285      return createSession(createDefaultSessionArgs());
286   }
287
288   @Override /* Parser */
289   public XmlParserSession createSession(ParserSessionArgs args) {
290      return new XmlParserSession(this, args);
291   }
292
293   //-----------------------------------------------------------------------------------------------------------------
294   // Extended metadata
295   //-----------------------------------------------------------------------------------------------------------------
296
297   @Override /* XmlMetaProvider */
298   public XmlClassMeta getXmlClassMeta(ClassMeta<?> cm) {
299      XmlClassMeta m = xmlClassMetas.get(cm);
300      if (m == null) {
301         m = new XmlClassMeta(cm, this);
302         xmlClassMetas.put(cm, m);
303      }
304      return m;
305   }
306
307   @Override /* XmlMetaProvider */
308   public XmlBeanMeta getXmlBeanMeta(BeanMeta<?> bm) {
309      XmlBeanMeta m = xmlBeanMetas.get(bm);
310      if (m == null) {
311         m = new XmlBeanMeta(bm, this);
312         xmlBeanMetas.put(bm, m);
313      }
314      return m;
315   }
316
317   @Override /* XmlMetaProvider */
318   public XmlBeanPropertyMeta getXmlBeanPropertyMeta(BeanPropertyMeta bpm) {
319      XmlBeanPropertyMeta m = xmlBeanPropertyMetas.get(bpm);
320      if (m == null) {
321         BeanPropertyMeta dbpm = bpm.getDelegateFor();
322         m = new XmlBeanPropertyMeta(dbpm, this);
323         xmlBeanPropertyMetas.put(bpm, m);
324      }
325      return m;
326   }
327
328   //-----------------------------------------------------------------------------------------------------------------
329   // Properties
330   //-----------------------------------------------------------------------------------------------------------------
331
332   /**
333    * XML event allocator.
334    *
335    * @see #XML_eventAllocator
336    * @return
337    *    The {@link XMLEventAllocator} associated with this parser, or <jk>null</jk> if there isn't one.
338    */
339   protected final XMLEventAllocator getEventAllocator() {
340      return eventAllocator;
341   }
342
343   /**
344    * Preserve root element during generalized parsing.
345    *
346    * @see #XML_preserveRootElement
347    * @return
348    *    <jk>true</jk> if when parsing into a generic {@link OMap}, the map will contain a single entry whose key
349    *    is the root element name.
350    */
351   protected final boolean isPreserveRootElement() {
352      return preserveRootElement;
353   }
354
355   /**
356    * XML reporter.
357    *
358    * @see #XML_reporter
359    * @return
360    *    The {@link XMLReporter} associated with this parser, or <jk>null</jk> if there isn't one.
361    */
362   protected final XMLReporter getReporter() {
363      return reporter;
364   }
365
366   /**
367    * XML resolver.
368    *
369    * @see #XML_resolver
370    * @return
371    *    The {@link XMLResolver} associated with this parser, or <jk>null</jk> if there isn't one.
372    */
373   protected final XMLResolver getResolver() {
374      return resolver;
375   }
376
377   /**
378    * Enable validation.
379    *
380    * @see #XML_validating
381    * @return
382    *    <jk>true</jk> if XML document will be validated.
383    */
384   protected final boolean isValidating() {
385      return validating;
386   }
387
388   //-----------------------------------------------------------------------------------------------------------------
389   // Other methods
390   //-----------------------------------------------------------------------------------------------------------------
391
392   @Override /* Context */
393   public OMap toMap() {
394      return super.toMap()
395         .a("XmlParser", new DefaultFilteringOMap()
396            .a("validating", validating)
397            .a("preserveRootElement", preserveRootElement)
398            .a("reporter", reporter)
399            .a("resolver", resolver)
400            .a("eventAllocator", eventAllocator)
401         );
402   }
403}