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