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