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<{@link javax.xml.stream.util.XMLEventAllocator}></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>"<root><a>foobar</a></root>"</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<{@link javax.xml.stream.XMLReporter}></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<{@link javax.xml.stream.XMLResolver}></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>"*​/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}