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 = cXml(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 = cXml(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 = bpXml(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 = bpXml(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 isMixed 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 isMixed, 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 = isMixed ? 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 if (o != null) { 275 276 if (aType.isDelegate()) { 277 wType = aType; 278 eType = aType = ((Delegate<?>)o).getClassMeta(); 279 } 280 281 sType = aType; 282 283 // Swap if necessary 284 PojoSwap swap = aType.getPojoSwap(this); 285 if (swap != null) { 286 o = swap(swap, o); 287 sType = swap.getSwapClassMeta(this); 288 289 // If the getSwapClass() method returns Object, we need to figure out 290 // the actual type now. 291 if (sType.isObject()) 292 sType = getClassMetaForObject(o); 293 } 294 } else { 295 sType = eType.getSerializedClassMeta(this); 296 } 297 298 // Does the actual type match the expected type? 299 boolean isExpectedType = true; 300 if (o == null || ! eType.same(aType)) { 301 if (eType.isNumber()) 302 isExpectedType = aType.isNumber(); 303 else if (eType.isMap()) 304 isExpectedType = aType.isMap(); 305 else if (eType.isCollectionOrArray()) 306 isExpectedType = aType.isCollectionOrArray(); 307 else 308 isExpectedType = false; 309 } 310 311 String resolvedDictionaryName = isExpectedType ? null : aType.getDictionaryName(); 312 313 // Note that the dictionary name may be specified on the actual type or the serialized type. 314 // HTML templates will have them defined on the serialized type. 315 String dictionaryName = aType.getDictionaryName(); 316 if (dictionaryName == null) 317 dictionaryName = sType.getDictionaryName(); 318 319 // char '\0' is interpreted as null. 320 if (o != null && sType.isChar() && ((Character)o).charValue() == 0) 321 o = null; 322 323 boolean isCollapsed = false; // If 'true', this is a collection and we're not rendering the outer element. 324 boolean isRaw = (sType.isReader() || sType.isInputStream()) && o != null; 325 326 // Get the JSON type string. 327 if (o == null) { 328 type = NULL; 329 } else if (sType.isCharSequence() || sType.isChar()) { 330 type = STRING; 331 } else if (sType.isNumber()) { 332 type = NUMBER; 333 } else if (sType.isBoolean()) { 334 type = BOOLEAN; 335 } else if (sType.isMapOrBean()) { 336 isCollapsed = cXml(sType).getFormat() == COLLAPSED; 337 type = OBJECT; 338 } else if (sType.isCollectionOrArray()) { 339 isCollapsed = (format == COLLAPSED && ! addNamespaceUris); 340 type = ARRAY; 341 } else { 342 type = STRING; 343 } 344 345 if (format.isOneOf(MIXED,MIXED_PWS,TEXT,TEXT_PWS,XMLTEXT) && type.isOneOf(NULL,STRING,NUMBER,BOOLEAN)) 346 isCollapsed = true; 347 348 // Is there a name associated with this bean? 349 if (elementName == null && dictionaryName != null) { 350 elementName = dictionaryName; 351 isExpectedType = true; 352 } 353 354 if (isEnableNamespaces()) { 355 if (elementNamespace == null) 356 elementNamespace = cXml(sType).getNamespace(); 357 if (elementNamespace == null) 358 elementNamespace = cXml(aType).getNamespace(); 359 if (elementNamespace != null && elementNamespace.uri == null) 360 elementNamespace = null; 361 if (elementNamespace == null) 362 elementNamespace = defaultNamespace; 363 } else { 364 elementNamespace = null; 365 } 366 367 // Do we need a carriage return after the start tag? 368 boolean cr = o != null && (sType.isMapOrBean() || sType.isCollectionOrArray()) && ! isMixed; 369 370 String en = elementName; 371 if (en == null && ! isRaw) { 372 en = type.toString(); 373 type = null; 374 } 375 boolean encodeEn = elementName != null; 376 String ns = (elementNamespace == null ? null : elementNamespace.name); 377 String dns = null, elementNs = null; 378 if (isEnableNamespaces()) { 379 dns = elementName == null && defaultNamespace != null ? defaultNamespace.name : null; 380 elementNs = elementName == null ? dns : ns; 381 if (elementName == null) 382 elementNamespace = null; 383 } 384 385 // Render the start tag. 386 if (! isCollapsed) { 387 if (en != null) { 388 out.oTag(i, elementNs, en, encodeEn); 389 if (addNamespaceUris) { 390 out.attr((String)null, "xmlns", defaultNamespace.getUri()); 391 392 for (Namespace n : namespaces) 393 out.attr("xmlns", n.getName(), n.getUri()); 394 } 395 if (! isExpectedType) { 396 if (resolvedDictionaryName != null) 397 out.attr(dns, getBeanTypePropertyName(eType), resolvedDictionaryName); 398 else if (type != null && type != STRING) 399 out.attr(dns, getBeanTypePropertyName(eType), type); 400 } 401 } else { 402 out.i(i); 403 } 404 if (o == null) { 405 if ((sType.isBoolean() || sType.isNumber()) && ! sType.isNullable()) 406 o = sType.getPrimitiveDefault(); 407 } 408 409 if (o != null && ! (sType.isMapOrBean() || en == null)) 410 out.append('>'); 411 412 if (cr && ! (sType.isMapOrBean())) 413 out.nl(i+1); 414 } 415 416 ContentResult rc = CR_ELEMENTS; 417 418 // Render the tag contents. 419 if (o != null) { 420 if (sType.isUri() || (pMeta != null && pMeta.isUri())) { 421 out.textUri(o); 422 } else if (sType.isCharSequence() || sType.isChar()) { 423 if (isXmlText(format, sType)) 424 out.append(o); 425 else 426 out.text(o, preserveWhitespace); 427 } else if (sType.isNumber() || sType.isBoolean()) { 428 out.append(o); 429 } else if (sType.isMap() || (wType != null && wType.isMap())) { 430 if (o instanceof BeanMap) 431 rc = serializeBeanMap(out, (BeanMap)o, elementNamespace, isCollapsed, isMixed); 432 else 433 rc = serializeMap(out, (Map)o, sType, eType.getKeyType(), eType.getValueType(), isMixed); 434 } else if (sType.isBean()) { 435 rc = serializeBeanMap(out, toBeanMap(o), elementNamespace, isCollapsed, isMixed); 436 } else if (sType.isCollection() || (wType != null && wType.isCollection())) { 437 if (isCollapsed) 438 this.indent--; 439 serializeCollection(out, o, sType, eType, pMeta, isMixed); 440 if (isCollapsed) 441 this.indent++; 442 } else if (sType.isArray()) { 443 if (isCollapsed) 444 this.indent--; 445 serializeCollection(out, o, sType, eType, pMeta, isMixed); 446 if (isCollapsed) 447 this.indent++; 448 } else if (sType.isReader() || sType.isInputStream()) { 449 IOUtils.pipe(o, out); 450 } else { 451 if (isXmlText(format, sType)) 452 out.append(toString(o)); 453 else 454 out.text(toString(o)); 455 } 456 } 457 458 pop(); 459 460 // Render the end tag. 461 if (! isCollapsed) { 462 if (en != null) { 463 if (rc == CR_EMPTY) { 464 if (isHtmlMode()) 465 out.append('>').eTag(elementNs, en, encodeEn); 466 else 467 out.append('/').append('>'); 468 } else if (rc == CR_VOID || o == null) { 469 out.append('/').append('>'); 470 } 471 else 472 out.ie(cr && rc != CR_MIXED ? i : 0).eTag(elementNs, en, encodeEn); 473 } 474 if (! isMixed) 475 out.nl(i); 476 } 477 478 return rc; 479 } 480 481 private boolean isXmlText(XmlFormat format, ClassMeta<?> sType) { 482 if (format == XMLTEXT) 483 return true; 484 XmlClassMeta xcm = sType.getExtendedMeta(XmlClassMeta.class); 485 if (xcm == null) 486 return false; 487 return xcm.getFormat() == XMLTEXT; 488 } 489 490 private ContentResult serializeMap(XmlWriter out, Map m, ClassMeta<?> sType, 491 ClassMeta<?> eKeyType, ClassMeta<?> eValueType, boolean isMixed) throws IOException, SerializeException { 492 493 m = sort(m); 494 495 ClassMeta<?> keyType = eKeyType == null ? sType.getKeyType() : eKeyType; 496 ClassMeta<?> valueType = eValueType == null ? sType.getValueType() : eValueType; 497 498 boolean hasChildren = false; 499 for (Iterator i = m.entrySet().iterator(); i.hasNext();) { 500 Map.Entry e = (Map.Entry)i.next(); 501 502 Object k = e.getKey(); 503 if (k == null) { 504 k = "\u0000"; 505 } else { 506 k = generalize(k, keyType); 507 if (isTrimStrings() && k instanceof String) 508 k = k.toString().trim(); 509 } 510 511 Object value = e.getValue(); 512 513 if (! hasChildren) { 514 hasChildren = true; 515 out.append('>').nlIf(! isMixed, indent); 516 } 517 serializeAnything(out, value, valueType, toString(k), null, false, XmlFormat.DEFAULT, isMixed, false, null); 518 } 519 return hasChildren ? CR_ELEMENTS : CR_EMPTY; 520 } 521 522 private ContentResult serializeBeanMap(XmlWriter out, BeanMap<?> m, 523 Namespace elementNs, boolean isCollapsed, boolean isMixed) throws IOException, SerializeException { 524 boolean hasChildren = false; 525 BeanMeta<?> bm = m.getMeta(); 526 527 List<BeanPropertyValue> lp = m.getValues(isTrimNullProperties()); 528 529 XmlBeanMeta xbm = bXml(bm); 530 531 Set<String> 532 attrs = xbm.getAttrPropertyNames(), 533 elements = xbm.getElementPropertyNames(), 534 collapsedElements = xbm.getCollapsedPropertyNames(); 535 String 536 attrsProperty = xbm.getAttrsPropertyName(), 537 contentProperty = xbm.getContentPropertyName(); 538 539 XmlFormat cf = null; 540 541 Object content = null; 542 ClassMeta<?> contentType = null; 543 for (BeanPropertyValue p : lp) { 544 String n = p.getName(); 545 if (attrs.contains(n) || attrs.contains("*") || n.equals(attrsProperty)) { 546 BeanPropertyMeta pMeta = p.getMeta(); 547 if (pMeta.canRead()) { 548 ClassMeta<?> cMeta = p.getClassMeta(); 549 550 String key = p.getName(); 551 Object value = p.getValue(); 552 Throwable t = p.getThrown(); 553 if (t != null) 554 onBeanGetterException(pMeta, t); 555 556 if (canIgnoreValue(cMeta, key, value)) 557 continue; 558 559 XmlBeanPropertyMeta bpXml = bpXml(pMeta); 560 Namespace ns = (isEnableNamespaces() && bpXml.getNamespace() != elementNs ? bpXml.getNamespace() : null); 561 562 if (pMeta.isUri() ) { 563 out.attrUri(ns, key, value); 564 } else if (n.equals(attrsProperty)) { 565 if (value instanceof BeanMap) { 566 BeanMap<?> bm2 = (BeanMap)value; 567 for (BeanPropertyValue p2 : bm2.getValues(true)) { 568 String key2 = p2.getName(); 569 Object value2 = p2.getValue(); 570 Throwable t2 = p2.getThrown(); 571 if (t2 != null) 572 onBeanGetterException(pMeta, t); 573 out.attr(ns, key2, value2); 574 } 575 } else /* Map */ { 576 Map m2 = (Map)value; 577 for (Map.Entry e : (Set<Map.Entry>)(m2.entrySet())) { 578 out.attr(ns, toString(e.getKey()), e.getValue()); 579 } 580 } 581 } else { 582 out.attr(ns, key, value); 583 } 584 } 585 } 586 } 587 588 boolean 589 hasContent = false, 590 preserveWhitespace = false, 591 isVoidElement = xbm.getContentFormat() == VOID; 592 593 for (BeanPropertyValue p : lp) { 594 BeanPropertyMeta pMeta = p.getMeta(); 595 if (pMeta.canRead()) { 596 ClassMeta<?> cMeta = p.getClassMeta(); 597 598 String n = p.getName(); 599 if (n.equals(contentProperty)) { 600 content = p.getValue(); 601 contentType = p.getClassMeta(); 602 hasContent = true; 603 cf = xbm.getContentFormat(); 604 if (cf.isOneOf(MIXED,MIXED_PWS,TEXT,TEXT_PWS,XMLTEXT)) 605 isMixed = true; 606 if (cf.isOneOf(MIXED_PWS, TEXT_PWS)) 607 preserveWhitespace = true; 608 if (contentType.isCollection() && ((Collection)content).isEmpty()) 609 hasContent = false; 610 else if (contentType.isArray() && Array.getLength(content) == 0) 611 hasContent = false; 612 } else if (elements.contains(n) || collapsedElements.contains(n) || elements.contains("*") || collapsedElements.contains("*") ) { 613 String key = p.getName(); 614 Object value = p.getValue(); 615 Throwable t = p.getThrown(); 616 if (t != null) 617 onBeanGetterException(pMeta, t); 618 619 if (canIgnoreValue(cMeta, key, value)) 620 continue; 621 622 if (! hasChildren) { 623 hasChildren = true; 624 out.appendIf(! isCollapsed, '>').nlIf(! isMixed, indent); 625 } 626 627 XmlBeanPropertyMeta bpXml = bpXml(pMeta); 628 serializeAnything(out, value, cMeta, key, bpXml.getNamespace(), false, bpXml.getXmlFormat(), isMixed, false, pMeta); 629 } 630 } 631 } 632 if (! hasContent) 633 return (hasChildren ? CR_ELEMENTS : isVoidElement ? CR_VOID : CR_EMPTY); 634 out.append('>').nlIf(! isMixed, indent); 635 636 // Serialize XML content. 637 if (content != null) { 638 if (contentType == null) { 639 } else if (contentType.isCollection()) { 640 Collection c = (Collection)content; 641 for (Iterator i = c.iterator(); i.hasNext();) { 642 Object value = i.next(); 643 serializeAnything(out, value, contentType.getElementType(), null, null, false, cf, isMixed, preserveWhitespace, null); 644 } 645 } else if (contentType.isArray()) { 646 Collection c = toList(Object[].class, content); 647 for (Iterator i = c.iterator(); i.hasNext();) { 648 Object value = i.next(); 649 serializeAnything(out, value, contentType.getElementType(), null, null, false, cf, isMixed, preserveWhitespace, null); 650 } 651 } else { 652 serializeAnything(out, content, contentType, null, null, false, cf, isMixed, preserveWhitespace, null); 653 } 654 } else { 655 if (! isTrimNullProperties()) { 656 if (! isMixed) 657 out.i(indent); 658 out.text(content); 659 if (! isMixed) 660 out.nl(indent); 661 } 662 } 663 return isMixed ? CR_MIXED : CR_ELEMENTS; 664 } 665 666 private XmlWriter serializeCollection(XmlWriter out, Object in, ClassMeta<?> sType, 667 ClassMeta<?> eType, BeanPropertyMeta ppMeta, boolean isMixed) throws IOException, SerializeException { 668 669 ClassMeta<?> eeType = eType.getElementType(); 670 671 Collection c = (sType.isCollection() ? (Collection)in : toList(sType.getInnerClass(), in)); 672 673 c = sort(c); 674 675 String type2 = null; 676 677 String eName = type2; 678 Namespace eNs = null; 679 680 if (ppMeta != null) { 681 XmlBeanPropertyMeta bpXml = bpXml(ppMeta); 682 eName = bpXml.getChildName(); 683 eNs = bpXml.getNamespace(); 684 } 685 686 for (Iterator i = c.iterator(); i.hasNext();) { 687 Object value = i.next(); 688 serializeAnything(out, value, eeType, eName, eNs, false, XmlFormat.DEFAULT, isMixed, false, null); 689 } 690 return out; 691 } 692 693 private static XmlClassMeta cXml(ClassMeta<?> cm) { 694 return cm.getExtendedMeta(XmlClassMeta.class); 695 } 696 697 private static XmlBeanPropertyMeta bpXml(BeanPropertyMeta pMeta) { 698 return pMeta == null ? XmlBeanPropertyMeta.DEFAULT : pMeta.getExtendedMeta(XmlBeanPropertyMeta.class); 699 } 700 701 private static XmlBeanMeta bXml(BeanMeta bm) { 702 return (XmlBeanMeta)bm.getExtendedMeta(XmlBeanMeta.class); 703 } 704 705 static enum JsonType { 706 STRING("string"),BOOLEAN("boolean"),NUMBER("number"),ARRAY("array"),OBJECT("object"),NULL("null"); 707 708 private final String value; 709 private JsonType(String value) { 710 this.value = value; 711 } 712 713 @Override 714 public String toString() { 715 return value; 716 } 717 718 boolean isOneOf(JsonType...types) { 719 for (JsonType type : types) 720 if (type == this) 721 return true; 722 return false; 723 } 724 } 725 726 /** 727 * Identifies what the contents were of a serialized bean. 728 */ 729 @SuppressWarnings("javadoc") 730 public static enum ContentResult { 731 CR_VOID, // No content...append "/>" to the start tag. 732 CR_EMPTY, // No content...append "/>" to the start tag if XML, "/></end>" if HTML. 733 CR_MIXED, // Mixed content...don't add whitespace. 734 CR_ELEMENTS // Elements...use normal whitespace rules. 735 } 736 737 //----------------------------------------------------------------------------------------------------------------- 738 // Properties 739 //----------------------------------------------------------------------------------------------------------------- 740 741 /** 742 * Configuration property: Add <js>"_type"</js> properties when needed. 743 * 744 * @see XmlSerializer#XML_addBeanTypes 745 * @return 746 * <jk>true</jk> if<js>"_type"</js> properties will be added to beans if their type cannot be inferred 747 * through reflection. 748 */ 749 @Override 750 protected boolean isAddBeanTypes() { 751 return ctx.isAddBeanTypes(); 752 } 753 754 /** 755 * Configuration property: Add namespace URLs to the root element. 756 * 757 * @see XmlSerializer#XML_addNamespaceUrisToRoot 758 * @return 759 * <jk>true</jk> if {@code xmlns:x} attributes are added to the root element for the default and all mapped namespaces. 760 */ 761 protected final boolean isAddNamespaceUrisToRoot() { 762 return ctx.isAddNamespaceUrlsToRoot(); 763 } 764 765 /** 766 * Configuration property: Auto-detect namespace usage. 767 * 768 * @see XmlSerializer#XML_autoDetectNamespaces 769 * @return 770 * <jk>true</jk> if namespace usage is detected before serialization. 771 */ 772 protected final boolean isAutoDetectNamespaces() { 773 return ctx.isAutoDetectNamespaces(); 774 } 775 776 /** 777 * Configuration property: Default namespace. 778 * 779 * @see XmlSerializer#XML_defaultNamespace 780 * @return 781 * The default namespace URI for this document. 782 */ 783 protected final Namespace getDefaultNamespace() { 784 return defaultNamespace; 785 } 786 787 /** 788 * Configuration property: Enable support for XML namespaces. 789 * 790 * @see XmlSerializer#XML_enableNamespaces 791 * @return 792 * <jk>false</jk> if XML output will not contain any namespaces regardless of any other settings. 793 */ 794 protected final boolean isEnableNamespaces() { 795 return ctx.isEnableNamespaces(); 796 } 797 798 /** 799 * Configuration property: Default namespaces. 800 * 801 * @see XmlSerializer#XML_namespaces 802 * @return 803 * The default list of namespaces associated with this serializer. 804 */ 805 protected final Namespace[] getNamespaces() { 806 return namespaces; 807 } 808 809 /** 810 * Configuration property: XMLSchema namespace. 811 * 812 * @see XmlSerializer#XML_xsNamespace 813 * @return 814 * The namespace for the <c>XMLSchema</c> namespace, used by the schema generated by the 815 * {@link org.apache.juneau.xmlschema.XmlSchemaSerializer} class. 816 */ 817 protected final Namespace getXsNamespace() { 818 return ctx.getXsNamespace(); 819 } 820 821 //----------------------------------------------------------------------------------------------------------------- 822 // Other methods 823 //----------------------------------------------------------------------------------------------------------------- 824 825 @Override /* Session */ 826 public ObjectMap toMap() { 827 return super.toMap() 828 .append("XmlSerializerSession", new DefaultFilteringObjectMap() 829 ); 830 } 831}