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 static org.apache.juneau.internal.ArrayUtils.*; 016import static org.apache.juneau.xml.XmlSerializer.*; 017import static org.apache.juneau.xml.XmlSerializerSession.ContentResult.*; 018import static org.apache.juneau.xml.XmlSerializerSession.JsonType.*; 019import static org.apache.juneau.xml.annotation.XmlFormat.*; 020 021import java.io.IOException; 022import java.lang.reflect.*; 023import java.util.*; 024 025import org.apache.juneau.*; 026import org.apache.juneau.internal.*; 027import org.apache.juneau.serializer.*; 028import org.apache.juneau.transform.*; 029import org.apache.juneau.xml.annotation.*; 030 031/** 032 * Session object that lives for the duration of a single use of {@link XmlSerializer}. 033 * 034 * <p> 035 * This class is NOT thread safe. 036 * It is typically discarded after one-time use although it can be reused within the same thread. 037 */ 038@SuppressWarnings({"unchecked","rawtypes"}) 039public class XmlSerializerSession extends WriterSerializerSession { 040 041 private final XmlSerializer ctx; 042 private Namespace 043 defaultNamespace; 044 private Namespace[] namespaces = new Namespace[0]; 045 046 /** 047 * Create a new session using properties specified in the context. 048 * 049 * @param ctx 050 * The context creating this session object. 051 * The context contains all the configuration settings for this object. 052 * @param args 053 * Runtime arguments. 054 * These specify session-level information such as locale and URI context. 055 * It also include session-level properties that override the properties defined on the bean and 056 * serializer contexts. 057 */ 058 protected XmlSerializerSession(XmlSerializer ctx, SerializerSessionArgs args) { 059 super(ctx, args); 060 this.ctx = ctx; 061 namespaces = getInstanceArrayProperty(XML_namespaces, Namespace.class, ctx.getNamespaces()); 062 defaultNamespace = findDefaultNamespace(getInstanceProperty(XML_defaultNamespace, Namespace.class, ctx.getDefaultNamespace())); 063 } 064 065 private Namespace findDefaultNamespace(Namespace n) { 066 if (n == null) 067 return null; 068 if (n.name != null && n.uri != null) 069 return n; 070 if (n.uri == null) { 071 for (Namespace n2 : getNamespaces()) 072 if (n2.name.equals(n.name)) 073 return n2; 074 } 075 if (n.name == null) { 076 for (Namespace n2 : getNamespaces()) 077 if (n2.uri.equals(n.uri)) 078 return n2; 079 } 080 return n; 081 } 082 083 /* 084 * Add a namespace to this session. 085 * 086 * @param ns The namespace being added. 087 */ 088 private void addNamespace(Namespace ns) { 089 if (ns == defaultNamespace) 090 return; 091 092 for (Namespace n : namespaces) 093 if (n == ns) 094 return; 095 096 if (defaultNamespace != null && (ns.uri.equals(defaultNamespace.uri) || ns.name.equals(defaultNamespace.name))) 097 defaultNamespace = ns; 098 else 099 namespaces = append(namespaces, ns); 100 } 101 102 /** 103 * Returns <jk>true</jk> if we're serializing HTML. 104 * 105 * <p> 106 * The difference in behavior is how empty non-void elements are handled. 107 * The XML serializer will produce a collapsed tag, whereas the HTML serializer will produce a start and end tag. 108 * 109 * @return <jk>true</jk> if we're generating HTML. 110 */ 111 protected boolean isHtmlMode() { 112 return false; 113 } 114 115 /** 116 * Converts the specified output target object to an {@link XmlWriter}. 117 * 118 * @param out The output target object. 119 * @return The output target object wrapped in an {@link XmlWriter}. 120 * @throws IOException Thrown by underlying stream. 121 */ 122 public final XmlWriter getXmlWriter(SerializerPipe out) throws IOException { 123 Object output = out.getRawOutput(); 124 if (output instanceof XmlWriter) 125 return (XmlWriter)output; 126 XmlWriter w = new XmlWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isTrimStrings(), getQuoteChar(), getUriResolver(), isEnableNamespaces(), defaultNamespace); 127 out.setWriter(w); 128 return w; 129 } 130 131 @Override /* Serializer */ 132 protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException { 133 if (isEnableNamespaces() && isAutoDetectNamespaces()) 134 findNsfMappings(o); 135 serializeAnything(getXmlWriter(out), o, getExpectedRootType(o), null, null, isEnableNamespaces() && isAddNamespaceUrisToRoot(), XmlFormat.DEFAULT, false, false, null); 136 } 137 138 /** 139 * Recursively searches for the XML namespaces on the specified POJO and adds them to the serializer context object. 140 * 141 * @param o The POJO to check. 142 * @throws SerializeException Thrown if bean recursion occurred. 143 */ 144 protected final void findNsfMappings(Object o) throws SerializeException { 145 ClassMeta<?> aType = null; // The actual type 146 147 try { 148 aType = push(null, o, null); 149 } catch (BeanRecursionException e) { 150 throw new SerializeException(e); 151 } 152 153 if (aType != null) { 154 Namespace ns = getXmlClassMeta(aType).getNamespace(); 155 if (ns != null) { 156 if (ns.uri != null) 157 addNamespace(ns); 158 else 159 ns = null; 160 } 161 } 162 163 // Handle recursion 164 if (aType != null && ! aType.isPrimitive()) { 165 166 BeanMap<?> bm = null; 167 if (aType.isBeanMap()) { 168 bm = (BeanMap<?>)o; 169 } else if (aType.isBean()) { 170 bm = toBeanMap(o); 171 } else if (aType.isDelegate()) { 172 ClassMeta<?> innerType = ((Delegate<?>)o).getClassMeta(); 173 Namespace ns = getXmlClassMeta(innerType).getNamespace(); 174 if (ns != null) { 175 if (ns.uri != null) 176 addNamespace(ns); 177 else 178 ns = null; 179 } 180 181 if (innerType.isBean()) { 182 for (BeanPropertyMeta bpm : innerType.getBeanMeta().getPropertyMetas()) { 183 if (bpm.canRead()) { 184 ns = getXmlBeanPropertyMeta(bpm).getNamespace(); 185 if (ns != null && ns.uri != null) 186 addNamespace(ns); 187 } 188 } 189 190 } else if (innerType.isMap()) { 191 for (Object o2 : ((Map<?,?>)o).values()) 192 findNsfMappings(o2); 193 } else if (innerType.isCollection()) { 194 for (Object o2 : ((Collection<?>)o)) 195 findNsfMappings(o2); 196 } 197 198 } else if (aType.isMap()) { 199 for (Object o2 : ((Map<?,?>)o).values()) 200 findNsfMappings(o2); 201 } else if (aType.isCollection()) { 202 for (Object o2 : ((Collection<?>)o)) 203 findNsfMappings(o2); 204 } else if (aType.isArray() && ! aType.getElementType().isPrimitive()) { 205 for (Object o2 : ((Object[])o)) 206 findNsfMappings(o2); 207 } 208 if (bm != null) { 209 for (BeanPropertyValue p : bm.getValues(isTrimNullProperties())) { 210 211 Namespace ns = getXmlBeanPropertyMeta(p.getMeta()).getNamespace(); 212 if (ns != null && ns.uri != null) 213 addNamespace(ns); 214 215 try { 216 findNsfMappings(p.getValue()); 217 } catch (Throwable x) { 218 // Ignore 219 } 220 } 221 } 222 } 223 224 pop(); 225 } 226 227 /** 228 * Workhorse method. 229 * 230 * @param out The writer to send the output to. 231 * @param o The object to serialize. 232 * @param eType The expected type if this is a bean property value being serialized. 233 * @param elementName The root element name. 234 * @param elementNamespace The namespace of the element. 235 * @param addNamespaceUris Flag indicating that namespace URIs need to be added. 236 * @param format The format to serialize the output to. 237 * @param isMixedOrText We're serializing mixed content, so don't use whitespace. 238 * @param preserveWhitespace 239 * <jk>true</jk> if we're serializing {@link XmlFormat#MIXED_PWS} or {@link XmlFormat#TEXT_PWS}. 240 * @param pMeta The bean property metadata if this is a bean property being serialized. 241 * @return The same writer passed in so that calls to the writer can be chained. 242 * @throws IOException Thrown by underlying stream. 243 * @throws SerializeException General serialization error occurred. 244 */ 245 protected ContentResult serializeAnything( 246 XmlWriter out, 247 Object o, 248 ClassMeta<?> eType, 249 String elementName, 250 Namespace elementNamespace, 251 boolean addNamespaceUris, 252 XmlFormat format, 253 boolean isMixedOrText, 254 boolean preserveWhitespace, 255 BeanPropertyMeta pMeta) throws IOException, SerializeException { 256 257 JsonType type = null; // The type string (e.g. <type> or <x x='type'> 258 int i = isMixedOrText ? 0 : indent; // Current indentation 259 ClassMeta<?> aType = null; // The actual type 260 ClassMeta<?> wType = null; // The wrapped type (delegate) 261 ClassMeta<?> sType = object(); // The serialized type 262 263 aType = push2(elementName, o, eType); 264 265 if (eType == null) 266 eType = object(); 267 268 // Handle recursion 269 if (aType == null) { 270 o = null; 271 aType = object(); 272 } 273 274 // Handle Optional<X> 275 if (isOptional(aType)) { 276 o = getOptionalValue(o); 277 eType = getOptionalType(eType); 278 aType = getClassMetaForObject(o, object()); 279 } 280 281 if (o != null) { 282 283 if (aType.isDelegate()) { 284 wType = aType; 285 eType = aType = ((Delegate<?>)o).getClassMeta(); 286 } 287 288 sType = aType; 289 290 // Swap if necessary 291 PojoSwap swap = aType.getPojoSwap(this); 292 if (swap != null) { 293 o = swap(swap, o); 294 sType = swap.getSwapClassMeta(this); 295 296 // If the getSwapClass() method returns Object, we need to figure out 297 // the actual type now. 298 if (sType.isObject()) 299 sType = getClassMetaForObject(o); 300 } 301 } else { 302 sType = eType.getSerializedClassMeta(this); 303 } 304 305 // Does the actual type match the expected type? 306 boolean isExpectedType = true; 307 if (o == null || ! eType.same(aType)) { 308 if (eType.isNumber()) 309 isExpectedType = aType.isNumber(); 310 else if (eType.isMap()) 311 isExpectedType = aType.isMap(); 312 else if (eType.isCollectionOrArray()) 313 isExpectedType = aType.isCollectionOrArray(); 314 else 315 isExpectedType = false; 316 } 317 318 String resolvedDictionaryName = isExpectedType ? null : aType.getDictionaryName(); 319 320 // Note that the dictionary name may be specified on the actual type or the serialized type. 321 // HTML templates will have them defined on the serialized type. 322 String dictionaryName = aType.getDictionaryName(); 323 if (dictionaryName == null) 324 dictionaryName = sType.getDictionaryName(); 325 326 // char '\0' is interpreted as null. 327 if (o != null && sType.isChar() && ((Character)o).charValue() == 0) 328 o = null; 329 330 boolean isCollapsed = false; // If 'true', this is a collection and we're not rendering the outer element. 331 boolean isRaw = (sType.isReader() || sType.isInputStream()) && o != null; 332 333 // Get the JSON type string. 334 if (o == null) { 335 type = NULL; 336 } else if (sType.isCharSequence() || sType.isChar()) { 337 type = STRING; 338 } else if (sType.isNumber()) { 339 type = NUMBER; 340 } else if (sType.isBoolean()) { 341 type = BOOLEAN; 342 } else if (sType.isMapOrBean()) { 343 isCollapsed = getXmlClassMeta(sType).getFormat() == COLLAPSED; 344 type = OBJECT; 345 } else if (sType.isCollectionOrArray()) { 346 isCollapsed = (format == COLLAPSED && ! addNamespaceUris); 347 type = ARRAY; 348 } else { 349 type = STRING; 350 } 351 352 if (format.isOneOf(MIXED,MIXED_PWS,TEXT,TEXT_PWS,XMLTEXT) && type.isOneOf(NULL,STRING,NUMBER,BOOLEAN)) 353 isCollapsed = true; 354 355 // Is there a name associated with this bean? 356 if (elementName == null && dictionaryName != null) { 357 elementName = dictionaryName; 358 isExpectedType = true; 359 } 360 361 if (isEnableNamespaces()) { 362 if (elementNamespace == null) 363 elementNamespace = getXmlClassMeta(sType).getNamespace(); 364 if (elementNamespace == null) 365 elementNamespace = getXmlClassMeta(aType).getNamespace(); 366 if (elementNamespace != null && elementNamespace.uri == null) 367 elementNamespace = null; 368 if (elementNamespace == null) 369 elementNamespace = defaultNamespace; 370 } else { 371 elementNamespace = null; 372 } 373 374 // Do we need a carriage return after the start tag? 375 boolean cr = o != null && (sType.isMapOrBean() || sType.isCollectionOrArray()) && ! isMixedOrText; 376 377 String en = elementName; 378 if (en == null && ! isRaw) { 379 en = type.toString(); 380 type = null; 381 } 382 boolean encodeEn = elementName != null; 383 String ns = (elementNamespace == null ? null : elementNamespace.name); 384 String dns = null, elementNs = null; 385 if (isEnableNamespaces()) { 386 dns = elementName == null && defaultNamespace != null ? defaultNamespace.name : null; 387 elementNs = elementName == null ? dns : ns; 388 if (elementName == null) 389 elementNamespace = null; 390 } 391 392 // Render the start tag. 393 if (! isCollapsed) { 394 if (en != null) { 395 out.oTag(i, elementNs, en, encodeEn); 396 if (addNamespaceUris) { 397 out.attr((String)null, "xmlns", defaultNamespace.getUri()); 398 399 for (Namespace n : namespaces) 400 out.attr("xmlns", n.getName(), n.getUri()); 401 } 402 if (! isExpectedType) { 403 if (resolvedDictionaryName != null) 404 out.attr(dns, getBeanTypePropertyName(eType), resolvedDictionaryName); 405 else if (type != null && type != STRING) 406 out.attr(dns, getBeanTypePropertyName(eType), type); 407 } 408 } else { 409 out.i(i); 410 } 411 if (o == null) { 412 if ((sType.isBoolean() || sType.isNumber()) && ! sType.isNullable()) 413 o = sType.getPrimitiveDefault(); 414 } 415 416 if (o != null && ! (sType.isMapOrBean() || en == null)) 417 out.append('>'); 418 419 if (cr && ! (sType.isMapOrBean())) 420 out.nl(i+1); 421 } 422 423 ContentResult rc = CR_ELEMENTS; 424 425 // Render the tag contents. 426 if (o != null) { 427 if (sType.isUri() || (pMeta != null && pMeta.isUri())) { 428 out.textUri(o); 429 } else if (sType.isCharSequence() || sType.isChar()) { 430 if (isXmlText(format, sType)) 431 out.append(o); 432 else 433 out.text(o, preserveWhitespace); 434 } else if (sType.isNumber() || sType.isBoolean()) { 435 out.append(o); 436 } else if (sType.isMap() || (wType != null && wType.isMap())) { 437 if (o instanceof BeanMap) 438 rc = serializeBeanMap(out, (BeanMap)o, elementNamespace, isCollapsed, isMixedOrText); 439 else 440 rc = serializeMap(out, (Map)o, sType, eType.getKeyType(), eType.getValueType(), isMixedOrText); 441 } else if (sType.isBean()) { 442 rc = serializeBeanMap(out, toBeanMap(o), elementNamespace, isCollapsed, isMixedOrText); 443 } else if (sType.isCollection() || (wType != null && wType.isCollection())) { 444 if (isCollapsed) 445 this.indent--; 446 serializeCollection(out, o, sType, eType, pMeta, isMixedOrText); 447 if (isCollapsed) 448 this.indent++; 449 } else if (sType.isArray()) { 450 if (isCollapsed) 451 this.indent--; 452 serializeCollection(out, o, sType, eType, pMeta, isMixedOrText); 453 if (isCollapsed) 454 this.indent++; 455 } else if (sType.isReader() || sType.isInputStream()) { 456 IOUtils.pipe(o, out); 457 } else { 458 if (isXmlText(format, sType)) 459 out.append(toString(o)); 460 else 461 out.text(toString(o)); 462 } 463 } 464 465 pop(); 466 467 // Render the end tag. 468 if (! isCollapsed) { 469 if (en != null) { 470 if (rc == CR_EMPTY) { 471 if (isHtmlMode()) 472 out.append('>').eTag(elementNs, en, encodeEn); 473 else 474 out.append('/').append('>'); 475 } else if (rc == CR_VOID || o == null) { 476 out.append('/').append('>'); 477 } 478 else 479 out.ie(cr && rc != CR_MIXED ? i : 0).eTag(elementNs, en, encodeEn); 480 } 481 if (! isMixedOrText) 482 out.nl(i); 483 } 484 485 return rc; 486 } 487 488 private boolean isXmlText(XmlFormat format, ClassMeta<?> sType) { 489 if (format == XMLTEXT) 490 return true; 491 XmlClassMeta xcm = getXmlClassMeta(sType); 492 if (xcm == null) 493 return false; 494 return xcm.getFormat() == XMLTEXT; 495 } 496 497 private ContentResult serializeMap(XmlWriter out, Map m, ClassMeta<?> sType, 498 ClassMeta<?> eKeyType, ClassMeta<?> eValueType, boolean isMixed) throws IOException, SerializeException { 499 500 m = sort(m); 501 502 ClassMeta<?> keyType = eKeyType == null ? sType.getKeyType() : eKeyType; 503 ClassMeta<?> valueType = eValueType == null ? sType.getValueType() : eValueType; 504 505 boolean hasChildren = false; 506 for (Iterator i = m.entrySet().iterator(); i.hasNext();) { 507 Map.Entry e = (Map.Entry)i.next(); 508 509 Object k = e.getKey(); 510 if (k == null) { 511 k = "\u0000"; 512 } else { 513 k = generalize(k, keyType); 514 if (isTrimStrings() && k instanceof String) 515 k = k.toString().trim(); 516 } 517 518 Object value = e.getValue(); 519 520 if (! hasChildren) { 521 hasChildren = true; 522 out.append('>').nlIf(! isMixed, indent); 523 } 524 serializeAnything(out, value, valueType, toString(k), null, false, XmlFormat.DEFAULT, isMixed, false, null); 525 } 526 return hasChildren ? CR_ELEMENTS : CR_EMPTY; 527 } 528 529 private ContentResult serializeBeanMap(XmlWriter out, BeanMap<?> m, 530 Namespace elementNs, boolean isCollapsed, boolean isMixedOrText) throws IOException, SerializeException { 531 boolean hasChildren = false; 532 BeanMeta<?> bm = m.getMeta(); 533 534 List<BeanPropertyValue> lp = m.getValues(isTrimNullProperties()); 535 536 XmlBeanMeta xbm = getXmlBeanMeta(bm); 537 538 Set<String> 539 attrs = xbm.getAttrPropertyNames(), 540 elements = xbm.getElementPropertyNames(), 541 collapsedElements = xbm.getCollapsedPropertyNames(); 542 String 543 attrsProperty = xbm.getAttrsPropertyName(), 544 contentProperty = xbm.getContentPropertyName(); 545 546 XmlFormat cf = null; 547 548 Object content = null; 549 ClassMeta<?> contentType = null; 550 for (BeanPropertyValue p : lp) { 551 String n = p.getName(); 552 if (attrs.contains(n) || attrs.contains("*") || n.equals(attrsProperty)) { 553 BeanPropertyMeta pMeta = p.getMeta(); 554 if (pMeta.canRead()) { 555 ClassMeta<?> cMeta = p.getClassMeta(); 556 557 String key = p.getName(); 558 Object value = p.getValue(); 559 Throwable t = p.getThrown(); 560 if (t != null) 561 onBeanGetterException(pMeta, t); 562 563 if (canIgnoreValue(cMeta, key, value)) 564 continue; 565 566 XmlBeanPropertyMeta bpXml = getXmlBeanPropertyMeta(pMeta); 567 Namespace ns = (isEnableNamespaces() && bpXml.getNamespace() != elementNs ? bpXml.getNamespace() : null); 568 569 if (pMeta.isUri() ) { 570 out.attrUri(ns, key, value); 571 } else if (n.equals(attrsProperty)) { 572 if (value instanceof BeanMap) { 573 BeanMap<?> bm2 = (BeanMap)value; 574 for (BeanPropertyValue p2 : bm2.getValues(true)) { 575 String key2 = p2.getName(); 576 Object value2 = p2.getValue(); 577 Throwable t2 = p2.getThrown(); 578 if (t2 != null) 579 onBeanGetterException(pMeta, t); 580 out.attr(ns, key2, value2); 581 } 582 } else /* Map */ { 583 Map m2 = (Map)value; 584 if (m2 != null) 585 for (Map.Entry e : (Set<Map.Entry>)(m2.entrySet())) 586 out.attr(ns, toString(e.getKey()), e.getValue()); 587 } 588 } else { 589 out.attr(ns, key, value); 590 } 591 } 592 } 593 } 594 595 boolean 596 hasContent = false, 597 preserveWhitespace = false, 598 isVoidElement = xbm.getContentFormat() == VOID; 599 600 for (BeanPropertyValue p : lp) { 601 BeanPropertyMeta pMeta = p.getMeta(); 602 if (pMeta.canRead()) { 603 ClassMeta<?> cMeta = p.getClassMeta(); 604 605 String n = p.getName(); 606 if (n.equals(contentProperty)) { 607 content = p.getValue(); 608 contentType = p.getClassMeta(); 609 hasContent = true; 610 cf = xbm.getContentFormat(); 611 if (cf.isOneOf(MIXED,MIXED_PWS,TEXT,TEXT_PWS,XMLTEXT)) 612 isMixedOrText = true; 613 if (cf.isOneOf(MIXED_PWS, TEXT_PWS)) 614 preserveWhitespace = true; 615 if (contentType.isCollection() && ((Collection)content).isEmpty()) 616 hasContent = false; 617 else if (contentType.isArray() && Array.getLength(content) == 0) 618 hasContent = false; 619 } else if (elements.contains(n) || collapsedElements.contains(n) || elements.contains("*") || collapsedElements.contains("*") ) { 620 String key = p.getName(); 621 Object value = p.getValue(); 622 Throwable t = p.getThrown(); 623 if (t != null) 624 onBeanGetterException(pMeta, t); 625 626 if (canIgnoreValue(cMeta, key, value)) 627 continue; 628 629 if (! hasChildren) { 630 hasChildren = true; 631 out.appendIf(! isCollapsed, '>').nlIf(! isMixedOrText, indent); 632 } 633 634 XmlBeanPropertyMeta bpXml = getXmlBeanPropertyMeta(pMeta); 635 serializeAnything(out, value, cMeta, key, bpXml.getNamespace(), false, bpXml.getXmlFormat(), isMixedOrText, false, pMeta); 636 } 637 } 638 } 639 if (contentProperty == null && ! hasContent) 640 return (hasChildren ? CR_ELEMENTS : isVoidElement ? CR_VOID : CR_EMPTY); 641 642 // Serialize XML content. 643 if (content != null) { 644 out.append('>').nlIf(! isMixedOrText, indent); 645 if (contentType == null) { 646 } else if (contentType.isCollection()) { 647 Collection c = (Collection)content; 648 for (Iterator i = c.iterator(); i.hasNext();) { 649 Object value = i.next(); 650 serializeAnything(out, value, contentType.getElementType(), null, null, false, cf, isMixedOrText, preserveWhitespace, null); 651 } 652 } else if (contentType.isArray()) { 653 Collection c = toList(Object[].class, content); 654 for (Iterator i = c.iterator(); i.hasNext();) { 655 Object value = i.next(); 656 serializeAnything(out, value, contentType.getElementType(), null, null, false, cf, isMixedOrText, preserveWhitespace, null); 657 } 658 } else { 659 serializeAnything(out, content, contentType, null, null, false, cf, isMixedOrText, preserveWhitespace, null); 660 } 661 } else { 662 out.attr("nil", "true").append('>').nlIf(! isMixedOrText, indent); 663 } 664 return isMixedOrText ? CR_MIXED : CR_ELEMENTS; 665 } 666 667 private XmlWriter serializeCollection(XmlWriter out, Object in, ClassMeta<?> sType, 668 ClassMeta<?> eType, BeanPropertyMeta ppMeta, boolean isMixed) throws IOException, SerializeException { 669 670 ClassMeta<?> eeType = eType.getElementType(); 671 672 Collection c = (sType.isCollection() ? (Collection)in : toList(sType.getInnerClass(), in)); 673 674 c = sort(c); 675 676 String type2 = null; 677 678 String eName = type2; 679 Namespace eNs = null; 680 681 if (ppMeta != null) { 682 XmlBeanPropertyMeta bpXml = getXmlBeanPropertyMeta(ppMeta); 683 eName = bpXml.getChildName(); 684 eNs = bpXml.getNamespace(); 685 } 686 687 for (Iterator i = c.iterator(); i.hasNext();) { 688 Object value = i.next(); 689 serializeAnything(out, value, eeType, eName, eNs, false, XmlFormat.DEFAULT, isMixed, false, null); 690 } 691 return out; 692 } 693 694 static enum JsonType { 695 STRING("string"),BOOLEAN("boolean"),NUMBER("number"),ARRAY("array"),OBJECT("object"),NULL("null"); 696 697 private final String value; 698 private JsonType(String value) { 699 this.value = value; 700 } 701 702 @Override 703 public String toString() { 704 return value; 705 } 706 707 boolean isOneOf(JsonType...types) { 708 for (JsonType type : types) 709 if (type == this) 710 return true; 711 return false; 712 } 713 } 714 715 /** 716 * Identifies what the contents were of a serialized bean. 717 */ 718 @SuppressWarnings("javadoc") 719 public static enum ContentResult { 720 CR_VOID, // No content...append "/>" to the start tag. 721 CR_EMPTY, // No content...append "/>" to the start tag if XML, "/></end>" if HTML. 722 CR_MIXED, // Mixed content...don't add whitespace. 723 CR_ELEMENTS // Elements...use normal whitespace rules. 724 } 725 726 //----------------------------------------------------------------------------------------------------------------- 727 // Properties 728 //----------------------------------------------------------------------------------------------------------------- 729 730 /** 731 * Configuration property: Add <js>"_type"</js> properties when needed. 732 * 733 * @see XmlSerializer#XML_addBeanTypes 734 * @return 735 * <jk>true</jk> if<js>"_type"</js> properties will be added to beans if their type cannot be inferred 736 * through reflection. 737 */ 738 @Override 739 protected boolean isAddBeanTypes() { 740 return ctx.isAddBeanTypes(); 741 } 742 743 /** 744 * Configuration property: Add namespace URLs to the root element. 745 * 746 * @see XmlSerializer#XML_addNamespaceUrisToRoot 747 * @return 748 * <jk>true</jk> if {@code xmlns:x} attributes are added to the root element for the default and all mapped namespaces. 749 */ 750 protected final boolean isAddNamespaceUrisToRoot() { 751 return ctx.isAddNamespaceUrlsToRoot(); 752 } 753 754 /** 755 * Configuration property: Auto-detect namespace usage. 756 * 757 * @see XmlSerializer#XML_autoDetectNamespaces 758 * @return 759 * <jk>true</jk> if namespace usage is detected before serialization. 760 */ 761 protected final boolean isAutoDetectNamespaces() { 762 return ctx.isAutoDetectNamespaces(); 763 } 764 765 /** 766 * Configuration property: Default namespace. 767 * 768 * @see XmlSerializer#XML_defaultNamespace 769 * @return 770 * The default namespace URI for this document. 771 */ 772 protected final Namespace getDefaultNamespace() { 773 return defaultNamespace; 774 } 775 776 /** 777 * Configuration property: Enable support for XML namespaces. 778 * 779 * @see XmlSerializer#XML_enableNamespaces 780 * @return 781 * <jk>false</jk> if XML output will not contain any namespaces regardless of any other settings. 782 */ 783 protected final boolean isEnableNamespaces() { 784 return ctx.isEnableNamespaces(); 785 } 786 787 /** 788 * Configuration property: Default namespaces. 789 * 790 * @see XmlSerializer#XML_namespaces 791 * @return 792 * The default list of namespaces associated with this serializer. 793 */ 794 protected final Namespace[] getNamespaces() { 795 return namespaces; 796 } 797 798 /** 799 * Configuration property: XMLSchema namespace. 800 * 801 * @see XmlSerializer#XML_xsNamespace 802 * @return 803 * The namespace for the <c>XMLSchema</c> namespace, used by the schema generated by the 804 * {@link org.apache.juneau.xmlschema.XmlSchemaSerializer} class. 805 */ 806 protected final Namespace getXsNamespace() { 807 return ctx.getXsNamespace(); 808 } 809 810 //----------------------------------------------------------------------------------------------------------------- 811 // Extended metadata 812 //----------------------------------------------------------------------------------------------------------------- 813 814 /** 815 * Returns the language-specific metadata on the specified class. 816 * 817 * @param cm The class to return the metadata on. 818 * @return The metadata. 819 */ 820 public XmlClassMeta getXmlClassMeta(ClassMeta<?> cm) { 821 return ctx.getXmlClassMeta(cm); 822 } 823 824 /** 825 * Returns the language-specific metadata on the specified bean. 826 * 827 * @param bm The bean to return the metadata on. 828 * @return The metadata. 829 */ 830 public XmlBeanMeta getXmlBeanMeta(BeanMeta<?> bm) { 831 return ctx.getXmlBeanMeta(bm); 832 } 833 834 /** 835 * Returns the language-specific metadata on the specified bean property. 836 * 837 * @param bpm The bean property to return the metadata on. 838 * @return The metadata. 839 */ 840 public XmlBeanPropertyMeta getXmlBeanPropertyMeta(BeanPropertyMeta bpm) { 841 return bpm == null ? XmlBeanPropertyMeta.DEFAULT : ctx.getXmlBeanPropertyMeta(bpm); 842 } 843 844 //----------------------------------------------------------------------------------------------------------------- 845 // Other methods 846 //----------------------------------------------------------------------------------------------------------------- 847 848 @Override /* Session */ 849 public ObjectMap toMap() { 850 return super.toMap() 851 .append("XmlSerializerSession", new DefaultFilteringObjectMap() 852 ); 853 } 854}