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 org.apache.juneau.*;
019import org.apache.juneau.annotation.*;
020import org.apache.juneau.json.*;
021import org.apache.juneau.serializer.*;
022import org.apache.juneau.xmlschema.XmlSchemaSerializer;
023
024/**
025 * Serializes POJO models to XML.
026 *
027 * <h5 class='topic'>Media types</h5>
028 *
029 * Handles <c>Accept</c> types:  <bc>text/xml</bc>
030 * <p>
031 * Produces <c>Content-Type</c> types:  <bc>text/xml</bc>
032 *
033 * <h5 class='topic'>Description</h5>
034 *
035 * See the {@link JsonSerializer} class for details on how Java models map to JSON.
036 *
037 * <p>
038 * For example, the following JSON...
039 * <p class='bcode w800'>
040 *    {
041 *       name:<js>'John Smith'</js>,
042 *       address: {
043 *          streetAddress: <js>'21 2nd Street'</js>,
044 *          city: <js>'New York'</js>,
045 *          state: <js>'NY'</js>,
046 *          postalCode: <js>10021</js>
047 *       },
048 *       phoneNumbers: [
049 *          <js>'212 555-1111'</js>,
050 *          <js>'212 555-2222'</js>
051 *       ],
052 *       additionalInfo: <jk>null</jk>,
053 *       remote: <jk>false</jk>,
054 *       height: <js>62.4</js>,
055 *       <js>'fico score'</js>:  <js>' &gt; 640'</js>
056 *    }
057 * <p>
058 *    ...maps to the following XML using the default serializer...
059 * <p class='bcode w800'>
060 *    <xt>&lt;object&gt;</xt>
061 *       <xt>&lt;name&gt;</xt>John Smith<xt>&lt;/name&gt;</xt>
062 *       <xt>&lt;address&gt;</xt>
063 *          <xt>&lt;streetAddress&gt;</xt>21 2nd Street<xt>&lt;/streetAddress&gt;</xt>
064 *          <xt>&lt;city&gt;</xt>New York<xt>&lt;/city&gt;</xt>
065 *          <xt>&lt;state&gt;</xt>NY<xt>&lt;/state&gt;</xt>
066 *          <xt>&lt;postalCode&gt;</xt>10021<xt>&lt;/postalCode&gt;</xt>
067 *       <xt>&lt;/address&gt;</xt>
068 *       <xt>&lt;phoneNumbers&gt;</xt>
069 *          <xt>&lt;string&gt;</xt>212 555-1111<xt>&lt;/string&gt;</xt>
070 *          <xt>&lt;string&gt;</xt>212 555-2222<xt>&lt;/string&gt;</xt>
071 *       <xt>&lt;/phoneNumbers&gt;</xt>
072 *       <xt>&lt;additionalInfo</xt> <xa>_type</xa>=<xs>'null'</xs><xt>&gt;&lt;/additionalInfo&gt;</xt>
073 *       <xt>&lt;remote&gt;</xt>false<xt>&lt;/remote&gt;</xt>
074 *       <xt>&lt;height&gt;</xt>62.4<xt>&lt;/height&gt;</xt>
075 *       <xt>&lt;fico_x0020_score&gt;</xt> &amp;gt; 640<xt>&lt;/fico_x0020_score&gt;</xt>
076 *    <xt>&lt;/object&gt;</xt>
077 *
078 * <p>
079 * An additional "add-json-properties" mode is also provided to prevent loss of JSON data types...
080 * <p class='bcode w800'>
081 *    <xt>&lt;object&gt;</xt>
082 *       <xt>&lt;name</xt> <xa>_type</xa>=<xs>'string'</xs><xt>&gt;</xt>John Smith<xt>&lt;/name&gt;</xt>
083 *       <xt>&lt;address</xt> <xa>_type</xa>=<xs>'object'</xs><xt>&gt;</xt>
084 *          <xt>&lt;streetAddress</xt> <xa>_type</xa>=<xs>'string'</xs><xt>&gt;</xt>21 2nd Street<xt>&lt;/streetAddress&gt;</xt>
085 *          <xt>&lt;city</xt> <xa>_type</xa>=<xs>'string'</xs><xt>&gt;</xt>New York<xt>&lt;/city&gt;</xt>
086 *          <xt>&lt;state</xt> <xa>_type</xa>=<xs>'string'</xs><xt>&gt;</xt>NY<xt>&lt;/state&gt;</xt>
087 *          <xt>&lt;postalCode</xt> <xa>_type</xa>=<xs>'number'</xs><xt>&gt;</xt>10021<xt>&lt;/postalCode&gt;</xt>
088 *       <xt>&lt;/address&gt;</xt>
089 *       <xt>&lt;phoneNumbers</xt> <xa>_type</xa>=<xs>'array'</xs><xt>&gt;</xt>
090 *          <xt>&lt;string&gt;</xt>212 555-1111<xt>&lt;/string&gt;</xt>
091 *          <xt>&lt;string&gt;</xt>212 555-2222<xt>&lt;/string&gt;</xt>
092 *       <xt>&lt;/phoneNumbers&gt;</xt>
093 *       <xt>&lt;additionalInfo</xt> <xa>_type</xa>=<xs>'null'</xs><xt>&gt;&lt;/additionalInfo&gt;</xt>
094 *       <xt>&lt;remote</xt> <xa>_type</xa>=<xs>'boolean'</xs><xt>&gt;</xt>false<xt>&lt;/remote&gt;</xt>
095 *       <xt>&lt;height</xt> <xa>_type</xa>=<xs>'number'</xs><xt>&gt;</xt>62.4<xt>&lt;/height&gt;</xt>
096 *       <xt>&lt;fico_x0020_score</xt> <xa>_type</xa>=<xs>'string'</xs><xt>&gt;</xt> &amp;gt; 640<xt>&lt;/fico_x0020_score&gt;</xt>
097 *    <xt>&lt;/object&gt;</xt>
098 * </p>
099 *
100 * <p>
101 * This serializer provides several serialization options.
102 * Typically, one of the predefined <jsf>DEFAULT</jsf> serializers will be sufficient.
103 * However, custom serializers can be constructed to fine-tune behavior.
104 *
105 * <p>
106 * If an attribute name contains any non-valid XML element characters, they will be escaped using standard
107 * {@code _x####_} notation.
108 *
109 * <h5 class='topic'>Behavior-specific subclasses</h5>
110 *
111 * The following direct subclasses are provided for convenience:
112 * <ul>
113 *    <li>{@link Sq} - Default serializer, single quotes.
114 *    <li>{@link SqReadable} - Default serializer, single quotes, whitespace added.
115 * </ul>
116 */
117@ConfigurableContext
118public class XmlSerializer extends WriterSerializer implements XmlMetaProvider, XmlCommon {
119
120   //-------------------------------------------------------------------------------------------------------------------
121   // Configurable properties
122   //-------------------------------------------------------------------------------------------------------------------
123
124   static final String PREFIX = "XmlSerializer";
125
126   /**
127    * Configuration property:  Add <js>"_type"</js> properties when needed.
128    *
129    * <h5 class='section'>Property:</h5>
130    * <ul class='spaced-list'>
131    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlSerializer#XML_addBeanTypes XML_addBeanTypes}
132    *    <li><b>Name:</b>  <js>"XmlSerializer.addBeanTypes.b"</js>
133    *    <li><b>Data type:</b>  <jk>boolean</jk>
134    *    <li><b>System property:</b>  <c>XmlSerializer.addBeanTypes</c>
135    *    <li><b>Environment variable:</b>  <c>XMLSERIALIZER_ADDBEANTYPES</c>
136    *    <li><b>Default:</b>  <jk>false</jk>
137    *    <li><b>Session property:</b>  <jk>false</jk>
138    *    <li><b>Annotations:</b>
139    *       <ul>
140    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#addBeanTypes()}
141    *       </ul>
142    *    <li><b>Methods:</b>
143    *       <ul>
144    *          <li class='jm'>{@link org.apache.juneau.xml.XmlSerializerBuilder#addBeanTypes(boolean)}
145    *       </ul>
146    * </ul>
147    *
148    * <h5 class='section'>Description:</h5>
149    * <p>
150    * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred
151    * through reflection.
152    *
153    * <p>
154    * When present, this value overrides the {@link #SERIALIZER_addBeanTypes} setting and is
155    * provided to customize the behavior of specific serializers in a {@link SerializerGroup}.
156    */
157   public static final String XML_addBeanTypes = PREFIX + ".addBeanTypes.b";
158
159   /**
160    * Configuration property:  Add namespace URLs to the root element.
161    *
162    * <h5 class='section'>Property:</h5>
163    * <ul class='spaced-list'>
164    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlSerializer#XML_addNamespaceUrisToRoot XML_addNamespaceUrisToRoot}
165    *    <li><b>Name:</b>  <js>"XmlSerializer.addNamespaceUrisToRoot.b"</js>
166    *    <li><b>Data type:</b>  <jk>boolean</jk>
167    *    <li><b>System property:</b>  <c>XmlSerializer.addNamespaceUrisToRoot</c>
168    *    <li><b>Environment variable:</b>  <c>XMLSERIALIZER_ADDNAMESPACEURISTOROOT</c>
169    *    <li><b>Default:</b>  <jk>false</jk>
170    *    <li><b>Session property:</b>  <jk>false</jk>
171    *    <li><b>Annotations:</b>
172    *       <ul>
173    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#addNamespaceUrisToRoot()}
174    *       </ul>
175    *    <li><b>Methods:</b>
176    *       <ul>
177    *          <li class='jm'>{@link org.apache.juneau.xml.XmlSerializerBuilder#addNamespaceUrisToRoot(boolean)}
178    *          <li class='jm'>{@link org.apache.juneau.xml.XmlSerializerBuilder#addNamespaceUrisToRoot()}
179    *       </ul>
180    * </ul>
181    *
182    * <h5 class='section'>Description:</h5>
183    * <p>
184    * Use this setting to add {@code xmlns:x} attributes to the root element for the default and all mapped namespaces.
185    *
186    * <p>
187    * This setting is ignored if {@link #XML_enableNamespaces} is not enabled.
188    *
189    * <ul class='seealso'>
190    *    <li class='link'>{@doc juneau-marshall.XmlDetails.Namespaces}
191    * </ul>
192    */
193   public static final String XML_addNamespaceUrisToRoot = PREFIX + ".addNamespaceUrisToRoot.b";
194
195   /**
196    * Configuration property:  Auto-detect namespace usage.
197    *
198    * <h5 class='section'>Property:</h5>
199    * <ul class='spaced-list'>
200    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlSerializer#XML_autoDetectNamespaces XML_autoDetectNamespaces}
201    *    <li><b>Name:</b>  <js>"XmlSerializer.autoDetectNamespaces.b"</js>
202    *    <li><b>Data type:</b>  <jk>boolean</jk>
203    *    <li><b>System property:</b>  <c>XmlSerializer.autoDetectNamespaces</c>
204    *    <li><b>Environment variable:</b>  <c>XMLSERIALIZER_AUTODETECTNAMESPACES</c>
205    *    <li><b>Default:</b>  <jk>true</jk>
206    *    <li><b>Session property:</b>  <jk>false</jk>
207    *    <li><b>Annotations:</b>
208    *       <ul>
209    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#autoDetectNamespaces()}
210    *       </ul>
211    *    <li><b>Methods:</b>
212    *       <ul>
213    *          <li class='jm'>{@link org.apache.juneau.xml.XmlSerializerBuilder#autoDetectNamespaces(boolean)}
214    *       </ul>
215    * </ul>
216    *
217    * <h5 class='section'>Description:</h5>
218    * <p>
219    * Detect namespace usage before serialization.
220    *
221    * <p>
222    * Used in conjunction with {@link #XML_addNamespaceUrisToRoot} to reduce the list of namespace URLs appended to the
223    * root element to only those that will be used in the resulting document.
224    *
225    * <p>
226    * If enabled, then the data structure will first be crawled looking for namespaces that will be encountered before
227    * the root element is serialized.
228    *
229    * <p>
230    * This setting is ignored if {@link #XML_enableNamespaces} is not enabled.
231    *
232    * <ul class='notes'>
233    *    <li>
234    *       Auto-detection of namespaces can be costly performance-wise.
235    *       <br>In high-performance environments, it's recommended that namespace detection be
236    *       disabled, and that namespaces be manually defined through the {@link #XML_namespaces} property.
237    * </ul>
238    *
239    * <ul class='seealso'>
240    *    <li class='link'>{@doc juneau-marshall.XmlDetails.Namespaces}
241    * </ul>
242    */
243   public static final String XML_autoDetectNamespaces = PREFIX + ".autoDetectNamespaces.b";
244
245   /**
246    * Configuration property:  Default namespace.
247    *
248    * <h5 class='section'>Property:</h5>
249    * <ul class='spaced-list'>
250    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlSerializer#XML_defaultNamespace XML_defaultNamespace}
251    *    <li><b>Name:</b>  <js>"XmlSerializer.defaultNamespace.s"</js>
252    *    <li><b>Data type:</b>  {@link org.apache.juneau.xml.Namespace}
253    *    <li><b>System property:</b>  <c>XmlSerializer.defaultNamespace</c>
254    *    <li><b>Environment variable:</b>  <c>XMLSERIALIZER_DEFAULTNAMESPACE</c>
255    *    <li><b>Default:</b>  <js>"juneau: http://www.apache.org/2013/Juneau"</js>
256    *    <li><b>Session property:</b>  <jk>false</jk>
257    *    <li><b>Annotations:</b>
258    *       <ul>
259    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#defaultNamespace()}
260    *       </ul>
261    *    <li><b>Methods:</b>
262    *       <ul>
263    *          <li class='jm'>{@link org.apache.juneau.xml.XmlSerializerBuilder#defaultNamespace(String)}
264    *       </ul>
265    * </ul>
266    *
267    * <h5 class='section'>Description:</h5>
268    * <p>
269    * Specifies the default namespace URI for this document.
270    *
271    * <ul class='seealso'>
272    *    <li class='link'>{@doc juneau-marshall.XmlDetails.Namespaces}
273    * </ul>
274    */
275   public static final String XML_defaultNamespace = PREFIX + ".defaultNamespace.s";
276
277   /**
278    * Configuration property:  Enable support for XML namespaces.
279    *
280    * <h5 class='section'>Property:</h5>
281    * <ul class='spaced-list'>
282    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlSerializer#XML_enableNamespaces XML_enableNamespaces}
283    *    <li><b>Name:</b>  <js>"XmlSerializer.enableNamespaces.b"</js>
284    *    <li><b>Data type:</b>  <jk>boolean</jk>
285    *    <li><b>System property:</b>  <c>XmlSerializer.enableNamespaces</c>
286    *    <li><b>Environment variable:</b>  <c>XMLSERIALIZER_ENABLENAMESPACES</c>
287    *    <li><b>Default:</b>  <jk>false</jk>
288    *    <li><b>Session property:</b>  <jk>false</jk>
289    *    <li><b>Annotations:</b>
290    *       <ul>
291    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#enableNamespaces()}
292    *       </ul>
293    *    <li><b>Methods:</b>
294    *       <ul>
295    *          <li class='jm'>{@link org.apache.juneau.xml.XmlSerializerBuilder#enableNamespaces(boolean)}
296    *       </ul>
297    * </ul>
298    *
299    * <h5 class='section'>Description:</h5>
300    * <p>
301    * If not enabled, XML output will not contain any namespaces regardless of any other settings.
302    *
303    * <ul class='seealso'>
304    *    <li class='link'>{@doc juneau-marshall.XmlDetails.Namespaces}
305    * </ul>
306    */
307   public static final String XML_enableNamespaces = PREFIX + ".enableNamespaces.b";
308
309   /**
310    * Configuration property:  Default namespaces.
311    *
312    * <h5 class='section'>Property:</h5>
313    * <ul class='spaced-list'>
314    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlSerializer#XML_namespaces XML_namespaces}
315    *    <li><b>Name:</b>  <js>"XmlSerializer.namespaces.ls"</js>
316    *    <li><b>Data type:</b>  <c>Set&lt;{@link org.apache.juneau.xml.Namespace}&gt;</c>
317    *    <li><b>System property:</b>  <c>XmlSerializer.namespaces</c>
318    *    <li><b>Environment variable:</b>  <c>XMLSERIALIZER_NAMESPACES</c>
319    *    <li><b>Default:</b>  empty set
320    *    <li><b>Session property:</b>  <jk>false</jk>
321    *    <li><b>Annotations:</b>
322    *       <ul>
323    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#defaultNamespace()}
324    *       </ul>
325    *    <li><b>Methods:</b>
326    *       <ul>
327    *          <li class='jm'>{@link org.apache.juneau.xml.XmlSerializerBuilder#defaultNamespace(String)}
328    *       </ul>
329    * </ul>
330    *
331    * <h5 class='section'>Description:</h5>
332    * <p>
333    * The default list of namespaces associated with this serializer.
334    *
335    * <ul class='seealso'>
336    *    <li class='link'>{@doc juneau-marshall.XmlDetails.Namespaces}
337    * </ul>
338    */
339   public static final String XML_namespaces = PREFIX + ".namespaces.ls";
340
341   /**
342    * Configuration property:  XMLSchema namespace.
343    *
344    * <h5 class='section'>Property:</h5>
345    * <ul class='spaced-list'>
346    *    <li><b>ID:</b>  {@link org.apache.juneau.xml.XmlSerializer#XML_xsNamespace XML_xsNamespace}
347    *    <li><b>Name:</b>  <js>"XmlSerializer.xsNamespace.s"</js>
348    *    <li><b>Data type:</b>  <c>String</c> ({@link org.apache.juneau.xml.Namespace})
349    *    <li><b>System property:</b>  <c>XmlSerializer.xsNamespace</c>
350    *    <li><b>Environment variable:</b>  <c>XMLSERIALIZER_XSNAMESPACE</c>
351    *    <li><b>Default:</b>  <js>"xs: http://www.w3.org/2001/XMLSchema"</js>
352    *    <li><b>Session property:</b>  <jk>false</jk>
353    *    <li><b>Annotations:</b>
354    *       <ul>
355    *          <li class='ja'>{@link org.apache.juneau.xml.annotation.XmlConfig#xsNamespace()}
356    *       </ul>
357    *    <li><b>Methods:</b>
358    *       <ul>
359    *          <li class='jm'>{@link org.apache.juneau.xml.XmlSerializerBuilder#xsNamespace(Namespace)}
360    *       </ul>
361    * </ul>
362    *
363    * <h5 class='section'>Description:</h5>
364    * <p>
365    * Specifies the namespace for the <c>XMLSchema</c> namespace, used by the schema generated by the
366    * {@link XmlSchemaSerializer} class.
367    *
368    * <ul class='seealso'>
369    *    <li class='link'>{@doc juneau-marshall.XmlDetails.Namespaces}
370    * </ul>
371    */
372   public static final String XML_xsNamespace = PREFIX + ".xsNamespace.s";
373
374
375   //-------------------------------------------------------------------------------------------------------------------
376   // Predefined instances
377   //-------------------------------------------------------------------------------------------------------------------
378
379   /** Default serializer without namespaces. */
380   public static final XmlSerializer DEFAULT = new XmlSerializer(PropertyStore.DEFAULT);
381
382   /** Default serializer without namespaces, with single quotes. */
383   public static final XmlSerializer DEFAULT_SQ = new Sq(PropertyStore.DEFAULT);
384
385   /** Default serializer without namespaces, with single quotes, whitespace added. */
386   public static final XmlSerializer DEFAULT_SQ_READABLE = new SqReadable(PropertyStore.DEFAULT);
387
388   /** Default serializer, all default settings. */
389   public static final XmlSerializer DEFAULT_NS = new Ns(PropertyStore.DEFAULT);
390
391   /** Default serializer, single quotes. */
392   public static final XmlSerializer DEFAULT_NS_SQ = new NsSq(PropertyStore.DEFAULT);
393
394   /** Default serializer, single quotes, whitespace added. */
395   public static final XmlSerializer DEFAULT_NS_SQ_READABLE = new NsSqReadable(PropertyStore.DEFAULT);
396
397
398   //-------------------------------------------------------------------------------------------------------------------
399   // Predefined subclasses
400   //-------------------------------------------------------------------------------------------------------------------
401
402   /** Default serializer, single quotes. */
403   public static class Sq extends XmlSerializer {
404
405      /**
406       * Constructor.
407       *
408       * @param ps The property store containing all the settings for this object.
409       */
410      public Sq(PropertyStore ps) {
411         super(
412            ps.builder()
413               .set(WSERIALIZER_quoteChar, '\'')
414               .build()
415            );
416      }
417   }
418
419   /** Default serializer, single quotes, whitespace added. */
420   public static class SqReadable extends XmlSerializer {
421
422      /**
423       * Constructor.
424       *
425       * @param ps The property store containing all the settings for this object.
426       */
427      public SqReadable(PropertyStore ps) {
428         super(
429            ps.builder()
430               .set(WSERIALIZER_quoteChar, '\'')
431               .set(WSERIALIZER_useWhitespace, true)
432               .build()
433            );
434      }
435   }
436
437   /** Default serializer without namespaces. */
438   public static class Ns extends XmlSerializer {
439
440      /**
441       * Constructor.
442       *
443       * @param ps The property store containing all the settings for this object.
444       */
445      public Ns(PropertyStore ps) {
446         super(
447            ps.builder()
448               .set(XML_enableNamespaces, true)
449               .build(),
450            "text/xml",
451            "text/xml+simple"
452         );
453      }
454   }
455
456   /** Default serializer without namespaces, single quotes. */
457   public static class NsSq extends XmlSerializer {
458
459      /**
460       * Constructor.
461       *
462       * @param ps The property store containing all the settings for this object.
463       */
464      public NsSq(PropertyStore ps) {
465         super(
466            ps.builder()
467               .set(XML_enableNamespaces, true)
468               .set(WSERIALIZER_quoteChar, '\'')
469               .build()
470            );
471      }
472   }
473
474   /** Default serializer without namespaces, single quotes, with whitespace. */
475   public static class NsSqReadable extends XmlSerializer {
476
477      /**
478       * Constructor.
479       *
480       * @param ps The property store containing all the settings for this object.
481       */
482      public NsSqReadable(PropertyStore ps) {
483         super(
484            ps.builder()
485               .set(XML_enableNamespaces, true)
486               .set(WSERIALIZER_quoteChar, '\'')
487               .set(WSERIALIZER_useWhitespace, true)
488               .build()
489            );
490      }
491   }
492
493   @SuppressWarnings("javadoc")
494   protected static final Namespace
495      DEFAULT_JUNEAU_NAMESPACE = Namespace.create("juneau", "http://www.apache.org/2013/Juneau"),
496      DEFAULT_XS_NAMESPACE = Namespace.create("xs", "http://www.w3.org/2001/XMLSchema");
497
498   //-------------------------------------------------------------------------------------------------------------------
499   // Instance
500   //-------------------------------------------------------------------------------------------------------------------
501
502   private final boolean
503      autoDetectNamespaces,
504      enableNamespaces,
505      addNamespaceUrlsToRoot,
506      addBeanTypes;
507   private final Namespace defaultNamespace;
508   private final Namespace
509      xsNamespace;
510   private final Namespace[] namespaces;
511   private final Map<ClassMeta<?>,XmlClassMeta> xmlClassMetas = new ConcurrentHashMap<>();
512   private final Map<BeanMeta<?>,XmlBeanMeta> xmlBeanMetas = new ConcurrentHashMap<>();
513   private final Map<BeanPropertyMeta,XmlBeanPropertyMeta> xmlBeanPropertyMetas = new ConcurrentHashMap<>();
514
515   private volatile XmlSchemaSerializer schemaSerializer;
516
517   /**
518    * Constructor.
519    *
520    * @param ps
521    *    The property store containing all the settings for this object.
522    */
523   public XmlSerializer(PropertyStore ps) {
524      this(ps, "text/xml", (String)null);
525   }
526
527   /**
528    * Constructor.
529    *
530    * @param ps
531    *    The property store containing all the settings for this object.
532    * @param produces
533    *    The media type that this serializer produces.
534    * @param accept
535    *    The accept media types that the serializer can handle.
536    *    <p>
537    *    Can contain meta-characters per the <c>media-type</c> specification of {@doc RFC2616.section14.1}
538    *    <p>
539    *    If empty, then assumes the only media type supported is <c>produces</c>.
540    *    <p>
541    *    For example, if this serializer produces <js>"application/json"</js> but should handle media types of
542    *    <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be:
543    *    <p class='bcode w800'>
544    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>);
545    *    </p>
546    *    <br>...or...
547    *    <p class='bcode w800'>
548    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*&#8203;/json"</js>);
549    *    </p>
550    * <p>
551    * The accept value can also contain q-values.
552    */
553   public XmlSerializer(PropertyStore ps, String produces, String accept) {
554      super(ps, produces, accept);
555      autoDetectNamespaces = getBooleanProperty(XML_autoDetectNamespaces, true);
556      enableNamespaces = getBooleanProperty(XML_enableNamespaces, false);
557      addNamespaceUrlsToRoot = getBooleanProperty(XML_addNamespaceUrisToRoot, false);
558      defaultNamespace = getInstanceProperty(XML_defaultNamespace, Namespace.class, DEFAULT_JUNEAU_NAMESPACE);
559      addBeanTypes = getBooleanProperty(XML_addBeanTypes, getBooleanProperty(SERIALIZER_addBeanTypes, false));
560      xsNamespace = getInstanceProperty(XML_xsNamespace, Namespace.class, DEFAULT_XS_NAMESPACE);
561      namespaces = getInstanceArrayProperty(XML_namespaces, Namespace.class, new Namespace[0]);
562   }
563
564   @Override /* Context */
565   public XmlSerializerBuilder builder() {
566      return new XmlSerializerBuilder(getPropertyStore());
567   }
568
569   /**
570    * Instantiates a new clean-slate {@link XmlSerializerBuilder} object.
571    *
572    * <p>
573    * This is equivalent to simply calling <code><jk>new</jk> XmlSerializerBuilder()</code>.
574    *
575    * <p>
576    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
577    * the settings of the object called on.
578    *
579    * @return A new {@link XmlSerializerBuilder} object.
580    */
581   public static XmlSerializerBuilder create() {
582      return new XmlSerializerBuilder();
583   }
584
585   /**
586    * Returns the schema serializer based on the settings of this serializer.
587    * @return The schema serializer.
588    */
589   public XmlSerializer getSchemaSerializer() {
590      if (schemaSerializer == null)
591         schemaSerializer = builder().build(XmlSchemaSerializer.class);
592      return schemaSerializer;
593   }
594
595   @Override /* Serializer */
596   public XmlSerializerSession createSession() {
597      return createSession(createDefaultSessionArgs());
598   }
599
600   @Override /* Serializer */
601   public XmlSerializerSession createSession(SerializerSessionArgs args) {
602      return new XmlSerializerSession(this, args);
603   }
604
605   //-----------------------------------------------------------------------------------------------------------------
606   // Extended metadata
607   //-----------------------------------------------------------------------------------------------------------------
608
609   @Override /* XmlMetaProvider */
610   public XmlClassMeta getXmlClassMeta(ClassMeta<?> cm) {
611      XmlClassMeta m = xmlClassMetas.get(cm);
612      if (m == null) {
613         m = new XmlClassMeta(cm, this);
614         xmlClassMetas.put(cm, m);
615      }
616      return m;
617   }
618
619   @Override /* XmlMetaProvider */
620   public XmlBeanMeta getXmlBeanMeta(BeanMeta<?> bm) {
621      XmlBeanMeta m = xmlBeanMetas.get(bm);
622      if (m == null) {
623         m = new XmlBeanMeta(bm, this);
624         xmlBeanMetas.put(bm, m);
625      }
626      return m;
627   }
628
629   @Override /* XmlMetaProvider */
630   public XmlBeanPropertyMeta getXmlBeanPropertyMeta(BeanPropertyMeta bpm) {
631      XmlBeanPropertyMeta m = xmlBeanPropertyMetas.get(bpm);
632      if (m == null) {
633         m = new XmlBeanPropertyMeta(bpm.getDelegateFor(), this);
634         xmlBeanPropertyMetas.put(bpm, m);
635      }
636      return m;
637   }
638
639   //-----------------------------------------------------------------------------------------------------------------
640   // Properties
641   //-----------------------------------------------------------------------------------------------------------------
642
643   /**
644    * Configuration property:  Add <js>"_type"</js> properties when needed.
645    *
646    * @see #XML_addBeanTypes
647    * @return
648    *    <jk>true</jk> if<js>"_type"</js> properties will be added to beans if their type cannot be inferred
649    *    through reflection.
650    */
651   @Override
652   protected boolean isAddBeanTypes() {
653      return addBeanTypes;
654   }
655
656   /**
657    * Configuration property:  Add namespace URLs to the root element.
658    *
659    * @see #XML_addNamespaceUrisToRoot
660    * @return
661    *    <jk>true</jk> if {@code xmlns:x} attributes are added to the root element for the default and all mapped namespaces.
662    */
663   protected final boolean isAddNamespaceUrlsToRoot() {
664      return addNamespaceUrlsToRoot;
665   }
666
667   /**
668    * Configuration property:  Auto-detect namespace usage.
669    *
670    * @see #XML_autoDetectNamespaces
671    * @return
672    *    <jk>true</jk> if namespace usage is detected before serialization.
673    */
674   protected final boolean isAutoDetectNamespaces() {
675      return autoDetectNamespaces;
676   }
677
678   /**
679    * Configuration property:  Default namespace.
680    *
681    * @see #XML_defaultNamespace
682    * @return
683    *    The default namespace URI for this document.
684    */
685   protected final Namespace getDefaultNamespace() {
686      return defaultNamespace;
687   }
688
689   /**
690    * Configuration property:  Enable support for XML namespaces.
691    *
692    * @see #XML_enableNamespaces
693    * @return
694    *    <jk>false</jk> if XML output will not contain any namespaces regardless of any other settings.
695    */
696   protected final boolean isEnableNamespaces() {
697      return enableNamespaces;
698   }
699
700   /**
701    * Configuration property:  Default namespaces.
702    *
703    * @see #XML_namespaces
704    * @return
705    *    The default list of namespaces associated with this serializer.
706    */
707   protected final Namespace[] getNamespaces() {
708      return namespaces;
709   }
710
711   /**
712    * Configuration property:  XMLSchema namespace.
713    *
714    * @see #XML_xsNamespace
715    * @return
716    *    The namespace for the <c>XMLSchema</c> namespace, used by the schema generated by the
717    *    {@link XmlSchemaSerializer} class.
718    */
719   protected final Namespace getXsNamespace() {
720      return xsNamespace;
721   }
722
723   //-----------------------------------------------------------------------------------------------------------------
724   // Other methods
725   //-----------------------------------------------------------------------------------------------------------------
726
727   @Override /* Context */
728   public ObjectMap toMap() {
729      return super.toMap()
730         .append("XmlSerializer", new DefaultFilteringObjectMap()
731            .append("autoDetectNamespaces", autoDetectNamespaces)
732            .append("enableNamespaces", enableNamespaces)
733            .append("addNamespaceUrlsToRoot", addNamespaceUrlsToRoot)
734            .append("defaultNamespace", defaultNamespace)
735            .append("xsNamespace", xsNamespace)
736            .append("namespaces", namespaces)
737            .append("addBeanTypes", addBeanTypes)
738         );
739   }
740}