1 /**
2 * Redistribution and use of this software and associated documentation
3 * ("Software"), with or without modification, are permitted provided
4 * that the following conditions are met:
5 *
6 * 1. Redistributions of source code must retain copyright
7 * statements and notices. Redistributions must also contain a
8 * copy of this document.
9 *
10 * 2. Redistributions in binary form must reproduce the
11 * above copyright notice, this list of conditions and the
12 * following disclaimer in the documentation and/or other
13 * materials provided with the distribution.
14 *
15 * 3. The name "Exolab" must not be used to endorse or promote
16 * products derived from this Software without prior written
17 * permission of Intalio, Inc. For written permission,
18 * please contact info@exolab.org.
19 *
20 * 4. Products derived from this Software may not be called "Exolab"
21 * nor may "Exolab" appear in their names without prior written
22 * permission of Intalio, Inc. Exolab is a registered
23 * trademark of Intalio, Inc.
24 *
25 * 5. Due credit should be given to the Exolab Project
26 * (http://www.exolab.org/).
27 *
28 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
32 * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39 * OF THE POSSIBILITY OF SUCH DAMAGE.
40 *
41 * Copyright 1999-2004 (C) Intalio, Inc. All Rights Reserved.
42 *
43 * This file was originally developed by Keith Visco during the
44 * course of employment at Intalio Inc.
45 * All portions of this file developed by Keith Visco after Jan 19 2005 are
46 * Copyright (C) 2005 Keith Visco. All Rights Reserved.
47 *
48 * $Id$
49 */
50 package org.exolab.castor.xml;
51
52 import java.io.IOException;
53 import java.io.PrintWriter;
54 import java.io.Writer;
55 import java.lang.reflect.Array;
56 import java.lang.reflect.Method;
57 import java.math.BigDecimal;
58 import java.util.ArrayList;
59 import java.util.Enumeration;
60 import java.util.HashSet;
61 import java.util.Iterator;
62 import java.util.List;
63 import java.util.Set;
64 import java.util.Stack;
65 import java.util.StringTokenizer;
66
67 import org.apache.commons.lang.StringUtils;
68 import org.apache.commons.logging.Log;
69 import org.apache.commons.logging.LogFactory;
70 import org.castor.core.util.Base64Encoder;
71 import org.castor.core.util.HexDecoder;
72 import org.castor.core.util.Messages;
73 import org.castor.mapping.BindingType;
74 import org.castor.mapping.MappingUnmarshaller;
75 import org.castor.xml.InternalContext;
76 import org.castor.xml.XMLProperties;
77 import org.exolab.castor.mapping.ClassDescriptor;
78 import org.exolab.castor.mapping.CollectionHandler;
79 import org.exolab.castor.mapping.FieldHandler;
80 import org.exolab.castor.mapping.MapHandler;
81 import org.exolab.castor.mapping.MapItem;
82 import org.exolab.castor.mapping.Mapping;
83 import org.exolab.castor.mapping.MappingException;
84 import org.exolab.castor.mapping.MappingLoader;
85 import org.exolab.castor.mapping.handlers.MapHandlers;
86 import org.exolab.castor.mapping.loader.CollectionHandlers;
87 import org.exolab.castor.types.AnyNode;
88 import org.exolab.castor.util.SafeStack;
89 import org.exolab.castor.xml.descriptors.RootArrayDescriptor;
90 import org.exolab.castor.xml.descriptors.StringClassDescriptor;
91 import org.exolab.castor.xml.handlers.DateFieldHandler;
92 import org.exolab.castor.xml.handlers.EnumFieldHandler;
93 import org.exolab.castor.xml.util.AnyNode2SAX2;
94 import org.exolab.castor.xml.util.AttributeSetImpl;
95 import org.exolab.castor.xml.util.DocumentHandlerAdapter;
96 import org.exolab.castor.xml.util.SAX2DOMHandler;
97 import org.exolab.castor.xml.util.StaxEventHandler;
98 import org.exolab.castor.xml.util.StaxStreamHandler;
99 import org.exolab.castor.xml.util.XMLClassDescriptorAdapter;
100 import org.exolab.castor.xml.util.XMLClassDescriptorImpl;
101 import org.exolab.castor.xml.util.XMLFieldDescriptorImpl;
102 import org.w3c.dom.Node;
103 import org.xml.sax.ContentHandler;
104 import org.xml.sax.DocumentHandler;
105 import org.xml.sax.SAXException;
106 import org.xml.sax.helpers.AttributesImpl;
107
108 import javax.xml.stream.XMLEventWriter;
109 import javax.xml.stream.XMLStreamWriter;
110
111 import javax.xml.transform.Result;
112 import javax.xml.transform.dom.DOMResult;
113 import javax.xml.transform.sax.SAXResult;
114 import javax.xml.transform.stream.StreamResult;
115
116 /**
117 * A Marshaller that serializes Java Object's to XML
118 *
119 * Note: This class is not thread safe, and not intended to be,
120 * so please create a new Marshaller for each thread if it
121 * is to be used in a multithreaded environment.
122 *
123 * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
124 * @version $Revision$ $Date: 2006-04-13 06:47:36 -0600 (Thu, 13 Apr 2006) $
125 */
126 public class Marshaller extends MarshalFramework {
127
128
129 //---------------------------/
130 //- Private Class variables -/
131 //---------------------------/
132
133 /**
134 * Logger from commons-logging.
135 */
136 private static final Log LOG = LogFactory.getLog(Marshaller.class);
137
138 /**
139 * The CDATA type..uses for SAX attributes.
140 **/
141 private static final String CDATA = "CDATA";
142
143 /**
144 * Default prefix for use when creating
145 * namespace prefixes.
146 **/
147 private static final String DEFAULT_PREFIX = "ns";
148
149
150 /**
151 * Message name for a non sax capable serializer error.
152 **/
153 private static final String SERIALIZER_NOT_SAX_CAPABLE
154 = "conf.serializerNotSaxCapable";
155
156 /**
157 * Namespace declaration for xml schema instance.
158 */
159 private static final String XSI_PREFIX = "xsi";
160
161 /**
162 * The xsi:type attribute.
163 */
164 private static final String XSI_TYPE = "xsi:type";
165
166 /**
167 * Namespace prefix counter.
168 */
169 private int _namespaceCounter = 0;
170
171 /**
172 * An instance of StringClassDescriptor.
173 */
174 private static final StringClassDescriptor STRING_CLASS_DESCRIPTOR
175 = new StringClassDescriptor();
176
177 //----------------------------/
178 //- Private member variables -/
179 //----------------------------/
180
181 /**
182 * A boolean to indicate whether or not we are
183 * marshalling as a complete document or not.
184 **/
185 private boolean _asDocument = true;
186
187 /**
188 * The depth of the sub tree, 0 denotes document level.
189 */
190 private int _depth = 0;
191
192 /**
193 * The output format to use with the serializer.
194 * This will be null if the user passed in their
195 * own DocumentHandler.
196 **/
197 private OutputFormat _format = null;
198
199 /**
200 * The ContentHandler we are marshaling to.
201 **/
202 private ContentHandler _handler = null;
203
204 /**
205 * Indicates whether whether or not to use xsi:type declarations in the output.
206 **/
207 private boolean _marshalExtendedType = true;
208
209 /**
210 * The registered {@link MarshalListener} to receive notifications of pre- and post marshal for
211 * each object in the tree being marshaled.
212 **/
213 private MarshalListener _marshalListener = null;
214
215 /**
216 * The name space stack.
217 **/
218 private NamespacesStack namespacesStack = new NamespacesStack();
219
220 /**
221 * Records Java packages being used during marshaling.
222 **/
223 private List<String> _packages = new ArrayList<String>();
224
225 /**
226 * A stack of parent objects...to prevent circular
227 * references from being marshaled.
228 **/
229 private Stack _parents = new SafeStack();
230
231 /**
232 * A list of ProcessingInstructions to output
233 * upon marshalling of the document.
234 **/
235 private List<ProcessingInstruction> _processingInstructions = new ArrayList<ProcessingInstruction>();
236
237 /**
238 * Name of the root element to use.
239 */
240 private String _rootElement = null;
241
242 /**
243 * A boolean to indicate keys from a map
244 * should be saved when necessary.
245 */
246 private boolean _saveMapKeys = true;
247
248 /**
249 * The serializer that is being used for marshalling.
250 * This may be null if the user passed in a DocumentHandler.
251 **/
252 private Serializer _serializer = null;
253
254 /**
255 * A flag to allow suppressing namespaces.
256 */
257 private boolean _suppressNamespaces = false;
258
259 /**
260 * A flag to allow suppressing the xsi:type attribute.
261 */
262 private boolean _suppressXSIType = false;
263
264 private boolean _useXSITypeAtRoot = false;
265
266 /**
267 * The set of optional top-level attributes
268 * set by the user.
269 **/
270 private AttributeSetImpl _topLevelAtts = new AttributeSetImpl();
271
272 /**
273 * The AttributeList which is to be used during marshalling,
274 * instead of creating a bunch of new ones.
275 */
276 private AttributesImpl _attributes = new AttributesImpl();
277
278 /**
279 * The validation flag.
280 */
281 private boolean _validate = false;
282
283 /** Set of full class names of proxy interfaces. If the class to be marshalled implements
284 * one of them the superclass will be marshalled instead of the class itself. */
285 private final Set<String> _proxyInterfaces = new HashSet<String>();
286
287 /**
288 * Creates a new {@link Marshaller} with the given SAX {@link DocumentHandler}.
289 *
290 * @param handler the SAX {@link DocumentHandler} to "marshal" to.
291 *
292 * @throws IllegalArgumentException if the given {@link DocumentHandler} is null
293 * @deprecate Please use {@link XMLContext#createMarshaller()} and
294 * {@link Marshaller#setDocumentHandler(DocumentHandler)} instead
295 *
296 * @see {@link XMLContext#createMarshaller()}
297 * @see {@link Marshaller#setDocumentHandler(DocumentHandler)}
298 * @see XMLContext
299 *
300 **/
301 public Marshaller(final DocumentHandler handler) {
302 super(null);
303 checkNotNull(handler, "The given 'org.sax.DocumentHandler' instance is null.");
304
305 setContentHandler(new DocumentHandlerAdapter(handler));
306 }
307
308 /**
309 * Sets the given SAX {@link DocumentHandler} to 'marshal' into.
310 *
311 * @param handler the SAX {@link DocumentHandler} to "marshal" to.
312 *
313 * @throws IllegalArgumentException if the given {@link DocumentHandler} is null
314 **/
315 public void setDocumentHandler(final DocumentHandler handler) {
316 checkNotNull(handler, "The given 'org.sax.DocumentHandler' instance is null.");
317
318 setContentHandler(new DocumentHandlerAdapter(handler));
319 }
320
321 /**
322 * Creates a new {@link Marshaller} with the given SAX {@link ContentHandler}.
323 *
324 * @param contentHandler the {@link ContentHandler} to "marshal" to.
325 * @throws IllegalArgumentException if the gievn {@link ContentHandler} is null
326 * @deprecate Please use {@link XMLContext#createMarshaller()} and
327 * {@link Marshaller#setContentHandler(ContentHandler)} instead
328 *
329 * @see {@link XMLContext#createMarshaller()}
330 * @see {@link Marshaller#setContentHandler(ContentHandler)}
331 * @see XMLContext
332 *
333 **/
334 public Marshaller(final ContentHandler contentHandler) {
335 super(null);
336 checkNotNull(contentHandler, "The given 'org.sax.ContentHandler' is null.");
337
338 setContentHandler(contentHandler);
339 }
340
341 /**
342 * The one {@link Marshaller} constructor that is used by {@link XMLContext} which
343 * sets an {@link InternalContext} that comes from outside. Writer or {@link ContentHandler}
344 * have to be set in a second step.
345 * @param internalContext the {@link InternalContext} to initialize the {@link Marshaller}
346 * instance with
347 */
348 public Marshaller(final InternalContext internalContext) {
349 super(internalContext);
350 }
351
352 /**
353 * Creates a default instance of Marshaller, where the sink needs to be set
354 * separately.
355 */
356 public Marshaller () {
357 super(null);
358 }
359
360 /**
361 * Creates a new {@link Marshaller} with the given writer.
362 * @param out the {@link Writer} to serialise to.
363 * @throws IllegalArgumentException if the given {@link Writer} is null
364 * @throws IOException If the given {@link Writer} instance cannot be opened.
365 * @deprecate Please use {@link XMLContext#createMarshaller()} and
366 * {@link Marshaller#setWriter(Writer)} instead
367 *
368 * @see {@link XMLContext#createMarshaller()}
369 * @see {@link Marshaller#setWriter(Writer)}
370 * @see XMLContext
371 *
372 **/
373 public Marshaller(final Writer out) throws IOException {
374 super(null);
375 setWriter(out);
376 }
377
378 /**
379 * Creates a new {@link Marshaller} with the given {@link XMLStreamWriter}.
380 *
381 * @param xmlStreamWriter the {@link XMLStreamWriter}
382 * @throws IllegalArgumentException if the given {@link XMLStreamWriter} is null
383 * @see {@link XMLContext#createMarshaller()}
384 * @see {@link Marshaller#setXmlStreamWriter(javax.xml.stream.XMLStreamWriter)}
385 * @see XMLContext
386 *
387 * @since 1.3.3
388 */
389 public Marshaller(XMLStreamWriter xmlStreamWriter) {
390 super(null);
391 setXmlStreamWriter(xmlStreamWriter);
392 }
393
394 /**
395 * Creates a new {@link Marshaller} with the given {@link XMLEventWriter}.
396 *
397 * @param xmlEventWriter the {@link XMLEventWriter}
398 * @throws IllegalArgumentException if the given {@link XMLEventWriter} is null
399 * @see {@link XMLContext#createMarshaller()}
400 * @see {@link Marshaller#setXmlEventWriter(javax.xml.stream.XMLEventWriter)}
401 * @see XMLContext
402 *
403 * @since 1.3.3
404 */
405 public Marshaller(XMLEventWriter xmlEventWriter) {
406 super(null);
407 setXmlEventWriter(xmlEventWriter);
408 }
409
410 /**
411 * Sets the java.io.Writer to be used during marshalling.
412 *
413 * @param out
414 * The writer to use for marshalling
415 *
416 * @throws IllegalArgumentException if out is null
417 * @throws IOException
418 * If there's a problem accessing the java.io.Writer provided
419 */
420 public void setWriter (final Writer out) throws IOException {
421 checkNotNull(out, "The given 'java.io.Writer' instance is null.");
422
423 configureSerializer(out);
424 }
425
426 /**
427 * Sets the {@link Result} into which the output xml will be written. Currently this method supports
428 * {@link DOMResult}, {@link SAXResult} and {@link StreamResult}.
429 *
430 * @param result the {@link Result} instance to set
431 *
432 * @throws IllegalArgumentException if the result is null or it is not supported
433 *
434 * @since 1.3.3
435 */
436 public void setResult(Result result) throws IOException {
437 checkNotNull(result, "The given 'javax.xml.transform.Result' instance is null.");
438
439 if(result instanceof DOMResult) {
440 DOMResult domResult = (DOMResult) result;
441
442 if(domResult.getNode() != null) {
443 // sets the dom node
444 setNode(domResult.getNode());
445 return;
446 }
447 } else if(result instanceof SAXResult) {
448 SAXResult saxResult = (SAXResult) result;
449
450 if(saxResult.getHandler() != null) {
451 // sets the content handler
452 setContentHandler(saxResult.getHandler());
453 return;
454 // TODO what to do with lexical handler ?
455 }
456 } else if (result instanceof StreamResult) {
457 StreamResult streamResult = (StreamResult) result;
458
459 if(streamResult.getWriter() != null) {
460 // sets the writer
461 setWriter(streamResult.getWriter());
462 return;
463 } else if(streamResult.getOutputStream() != null) {
464 // sets the output stream, wrapping it into a print writer instance
465 setWriter(new PrintWriter(streamResult.getOutputStream()));
466 return;
467 }
468 }
469
470 throw new IllegalArgumentException(
471 "The given 'javax.transofrm.xml.Result' is not supported, or were incorrectly instantiated.");
472 }
473
474
475 private void configureSerializer(Writer out) throws IOException
476 {
477 _serializer = getInternalContext().getSerializer();
478
479 if (_serializer == null)
480 throw new RuntimeException("Unable to obtain serializer");
481
482 _serializer.setOutputCharStream( out );
483
484 //-- Due to a Xerces Serializer bug that doesn't allow declaring
485 //-- multiple prefixes to the same namespace, we use the old
486 //-- DocumentHandler format and process namespaces ourselves
487 _handler = new DocumentHandlerAdapter(_serializer.asDocumentHandler());
488
489 if ( _handler == null ) {
490 String err = Messages.format( SERIALIZER_NOT_SAX_CAPABLE,
491 _serializer.getClass().getName() );
492 throw new RuntimeException( err );
493 }
494 }
495
496 /**
497 * Creates a new {@link Marshaller} for the given DOM {@link Node}.
498 *
499 * @param node the DOM {@link Node} to marshal into.
500 *
501 * @throws IllegalArgumentException if node is null
502 *
503 * @deprecate Please use {@link XMLContext#createMarshaller()} and
504 * {@link Marshaller#setNode(Node)} instead
505 *
506 * @see {@link XMLContext#createMarshaller()}
507 * @see {@link Marshaller#setNode(Node)}
508 * @see XMLContext
509 **/
510 public Marshaller(final Node node) {
511 super(null);
512 checkNotNull(node, "The given 'org.w3c.dom.Node' instance is null.");
513
514 setContentHandler(new DocumentHandlerAdapter(new SAX2DOMHandler(node)));
515 }
516
517 /**
518 * Sets the W3C {@link Node} instance to marshal to.
519 *
520 * @param node the DOM {@link Node} to marshal into.
521 *
522 * @throws IllegalArgumentException if node is null
523 **/
524 public void setNode(final Node node) {
525 checkNotNull(node, "The given 'org.w3c.dom.Node' instance is null.");
526
527 setContentHandler(new DocumentHandlerAdapter(new SAX2DOMHandler(node)));
528 }
529
530 /**
531 * Sets the {@link XMLStreamWriter} to use.
532 *
533 * @param xmlStreamWriter the {@link XMLStreamWriter} instance to use
534 *
535 * @throws IllegalArgumentException if the xmlStreamWriter is null
536 *
537 * @since 1.3.3
538 */
539 public void setXmlStreamWriter(XMLStreamWriter xmlStreamWriter) {
540 checkNotNull(xmlStreamWriter, "The given 'java.xml.stream.XMLStreamWriter' instance is null.");
541
542 setContentHandler(new StaxStreamHandler(xmlStreamWriter));
543 }
544
545 /**
546 * Sets the {@link XMLEventWriter} to use.
547 *
548 * @param xmlEventWriter the {@link XMLEventWriter} instance to use
549 *
550 * @throws IllegalArgumentException if the xmlEventReader is null
551 *
552 * @since 1.3.3
553 */
554 public void setXmlEventWriter(XMLEventWriter xmlEventWriter) {
555 checkNotNull(xmlEventWriter, "The given 'java.xml.stream.XMLEventWriter' instance is null.");
556
557 setContentHandler(new StaxEventHandler(xmlEventWriter));
558 }
559
560 /**
561 * To set the {@link InternalContext} to use, and to
562 * initialize {@link Marshaller} properties linked to it.
563 * @param internalContext the {@link InternalContext} to use
564 */
565 public void setInternalContext(final InternalContext internalContext) {
566 super.setInternalContext(internalContext);
567 deriveProperties();
568 }
569
570
571 /**
572 * Derive class-level properties from {@link XMLProperties} as defined
573 * {@link InternalContext}. This method will be called after a new
574 * {@link InternalContext} or a property has been set.
575 *
576 * @link #setInternalContext(InternalContext)
577 */
578 private void deriveProperties() {
579 _validate = getInternalContext().marshallingValidation();
580 _saveMapKeys = getInternalContext().getBooleanProperty(
581 XMLProperties.SAVE_MAP_KEYS).booleanValue();
582
583 String prop = getInternalContext().getStringProperty(
584 XMLProperties.PROXY_INTERFACES);
585 if (prop != null) {
586 StringTokenizer tokenizer = new StringTokenizer(prop, ", ");
587 while (tokenizer.hasMoreTokens()) {
588 _proxyInterfaces.add(tokenizer.nextToken());
589 }
590 }
591 }
592
593 /**
594 * Adds the given processing instruction data to the set of
595 * processing instructions to output during marshalling.
596 *
597 * @param target the processing instruction target
598 * @param data the processing instruction data
599 *
600 * @throws IllegalArgumentException if target is null or empty string or data is null
601 **/
602 public void addProcessingInstruction(String target, String data) {
603
604 checkNotEmpty(target, "The argument 'target' must not be null or empty.");
605 checkNotNull(data, "The argument 'data' must not be null.");
606
607 _processingInstructions.add(new ProcessingInstruction(target, data));
608 } //-- addProcessingInstruction
609
610 /**
611 * Sets the document type definition for the serializer. Note that this method
612 * cannot be called if you've passed in your own DocumentHandler.
613 *
614 * @param publicId the public identifier
615 * @param systemId the system identifier
616 */
617 public void setDoctype(String publicId, String systemId) {
618
619 if (_serializer != null) {
620 if (_format == null) {
621 _format = getInternalContext().getOutputFormat();
622 }
623 _format.setDoctype(publicId, systemId);
624 //-- reset output format, this needs to be done
625 //-- any time a change occurs to the format.
626 _serializer.setOutputFormat( _format );
627
628 setDocumentHandler();
629 }
630 else {
631 String error = "doctype cannot be set if you've passed in "+
632 "your own DocumentHandler";
633 throw new IllegalStateException(error);
634 }
635 } //-- setDoctype
636
637 /**
638 * Sets whether or not to marshal as a document which includes
639 * the XML declaration, and if necessary the DOCTYPE declaration.
640 * By default the Marshaller will marshal as a well formed
641 * XML document including the XML Declaration.
642 *
643 * If the given boolean is true, the Marshaller will marshal
644 * as a well formed XML fragment (no XML declaration or DOCTYPE).
645 *
646 * This method is basically the same as calling
647 * #setMarshalAsDocument(false);
648 *
649 * @param supressXMLDeclaration a boolean that when true
650 * includes that generated XML should not contain
651 * the XML declaration.
652 * @see #setMarshalAsDocument
653 */
654 public void setSupressXMLDeclaration(boolean supressXMLDeclaration) {
655 setMarshalAsDocument(!supressXMLDeclaration);
656 } //-- setSupressXMLDeclaration
657
658 /**
659 * Sets whether or not to marshal as a document which includes
660 * the XML declaration, and if necessary the DOCTYPE declaration.
661 * By default the Marshaller will marshal as a well formed
662 * XML document including the XML Declaration.
663 *
664 * If the given boolean is false, the Marshaller will marshal
665 * as a well formed XML fragment (no XML declaration or DOCTYPE).
666 *
667 * This method is basically the same as calling
668 * #setSupressXMLDeclaration(true);
669 *
670 * @param asDocument a boolean, when true, indicating to marshal
671 * as a complete XML document.
672 * @see #setSupressXMLDeclaration
673 */
674 public void setMarshalAsDocument(boolean asDocument) {
675
676 _asDocument = asDocument;
677
678 if (_serializer != null) {
679
680 if (_format == null) {
681 _format = getInternalContext().getOutputFormat();
682 }
683 _format.setOmitXMLDeclaration( ! asDocument );
684 _format.setOmitDocumentType( ! asDocument );
685
686 //-- reset output format, this needs to be done
687 //-- any time a change occurs to the format.
688 _serializer.setOutputFormat( _format );
689
690 setDocumentHandler();
691 }
692
693 } //-- setMarshalAsDocument
694
695 /**
696 * Sets the given mapping to be used by the marshalling Framework. If a resolver
697 * exists this mapping will be added to the existing ClassDescriptorResolver.
698 * Otherwise a new ClassDescriptorResolver will be created.
699 *
700 * @param mapping Mapping to using during marshalling.
701 */
702 public void setMapping(final Mapping mapping) throws MappingException {
703 // if (_cdResolver == null) {
704 // _cdResolver = (XMLClassDescriptorResolver) ClassDescriptorResolverFactory
705 // .createClassDescriptorResolver(BindingType.XML);
706 // }
707 if ((getInternalContext() == null)
708 || (getInternalContext().getXMLClassDescriptorResolver() == null)) {
709 String message = "No internal context or no class descriptor in context.";
710 LOG.warn(message);
711 throw new IllegalStateException(message);
712 }
713
714 MappingUnmarshaller mum = new MappingUnmarshaller();
715 MappingLoader resolver = mum.getMappingLoader(mapping, BindingType.XML);
716 getInternalContext().getXMLClassDescriptorResolver().setMappingLoader(resolver);
717 }
718
719 /**
720 * Sets an optional MarshalListener to recieve pre and post
721 * marshal notification for each Object in the tree.
722 * MarshalListener is only for complex objects that map
723 * into elements, simpleTypes and types that map into
724 * attributes do not cause any pre and post event notifications.
725 * Current only one (1) listener is allowed. If you need
726 * register multiple listeners, you will have to create
727 * your own master listener that will forward the
728 * event notifications and manage the multiple
729 * listeners.
730 *
731 * @param listener the MarshalListener to set.
732 **/
733 public void setMarshalListener(MarshalListener listener) {
734 _marshalListener = listener;
735 }
736
737 /**
738 * Sets the mapping for the given Namespace prefix.
739 *
740 * @param nsPrefix the namespace prefix
741 * @param nsURI the namespace that the prefix resolves to
742 *
743 * @throws IllegalArgumentException if nsURI is null or empty string
744 **/
745 public void setNamespaceMapping(final String nsPrefix, final String nsURI) {
746 checkNotEmpty(nsURI, "Namespace URI must be not null.");
747 namespacesStack.addNamespace(nsPrefix, nsURI);
748 }
749
750 /**
751 * Sets the name of the root element to use.
752 *
753 * @param rootElement The name of the root element to use.
754 */
755 public void setRootElement(final String rootElement) {
756 _rootElement = rootElement;
757 }
758
759 /**
760 * Returns the name of the root element to use
761 * @return Returns the name of the root element to use
762 */
763 public String getRootElement()
764 {
765 return _rootElement;
766 } //-- getRootElement
767
768 /**
769 * Set to True to declare the given namespace mappings at the root node. Default is False.
770 * @param nsPrefixAtRoot
771 * @deprecated
772 */
773 public void setNSPrefixAtRoot(boolean nsPrefixAtRoot)
774 {
775 // leaving for now...backward compatability
776 //_nsPrefixAtRoot = nsPrefixAtRoot;
777 }
778
779 /**
780 * Returns True if the given namespace mappings will be declared at the root node.
781 * @return Returns True if the given namespace mappings will be declared at the root node.
782 * @deprecated
783 */
784 public boolean getNSPrefixAtRoot()
785 {
786 return true;
787 }
788
789 /**
790 * Returns the ClassDescriptorResolver for use during marshalling
791 *
792 * @return the ClassDescriptorResolver
793 * @see #setResolver
794 */
795 public XMLClassDescriptorResolver getResolver() {
796
797 // if (_cdResolver == null) {
798 // _cdResolver = (XMLClassDescriptorResolver)
799 // ClassDescriptorResolverFactory.createClassDescriptorResolver(BindingType.XML);
800 // }
801 if ((getInternalContext() == null)
802 || (getInternalContext().getXMLClassDescriptorResolver() == null)) {
803 String message = "No internal context or no class descriptor in context.";
804 LOG.warn(message);
805 throw new IllegalStateException(message);
806 }
807 return getInternalContext().getXMLClassDescriptorResolver();
808
809 } //-- getResolver
810
811 /**
812 * Sets the ClassDescriptorResolver to use during marshalling.
813 *
814 * <BR />
815 * <B>Note:</B> This method will nullify any Mapping
816 * currently being used by this Marshaller
817 *
818 * @param cdr the ClassDescriptorResolver to use
819 * @see #setMapping
820 * @see #getResolver
821 */
822 public void setResolver(final XMLClassDescriptorResolver cdr) {
823
824 if (cdr != null) {
825 getInternalContext().setXMLClassDescriptorResolver(cdr);
826 // _cdResolver = cdr;
827 }
828
829 } //-- setResolver
830
831 /**
832 * Sets whether or not to validate the object model
833 * before marshalling. By default validation is enabled.
834 * This method is really for debugging.
835 * I do not recommend turning off validation, since
836 * you could marshal a document, which you can then
837 * not unmarshal. If you know the object model
838 * is guaranteed to be valid, disabling validation will
839 * improve performace.
840 *
841 * @param validate the boolean indicating whether or not to
842 * validate the object model before marshalling.
843 **/
844 public void setValidation(boolean validate) {
845 _validate = validate;
846 } //-- setValidation
847
848 public boolean getValidation() {
849 return _validate;
850 }
851
852 /**
853 * If True the marshaller will use the 'xsi:type' attribute
854 * to marshall a field value that extended the defined field type.
855 * Default is True.
856 */
857 public void setMarshalExtendedType(boolean marshalExtendedType)
858 {
859 _marshalExtendedType = marshalExtendedType;
860 } //-- setMarshalExtendedType
861
862
863 /**
864 * If True the marshaller will use the 'xsi:type' attribute
865 * to marshall a field value that extended the defined field type.
866 * Default is True.
867 * @return If True the marshaller will use the 'xsi:type' attribute
868 * to marshall a field value that extended the defined field type.
869 * Default is True.
870 */
871 public boolean getMarshalExtendedType()
872 {
873 return _marshalExtendedType;
874 } //-- setMarshallExtendedType
875
876 /**
877 * Marshals the given Object as XML using the given writer.
878 *
879 * @param object The Object to marshal.
880 * @param out The writer to marshal to.
881 * @exception org.exolab.castor.xml.MarshalException
882 * @exception org.exolab.castor.xml.ValidationException
883 */
884 public static void marshal(Object object, Writer out)
885 throws MarshalException, ValidationException {
886 try {
887 staticMarshal(object, new Marshaller(out));
888 } catch (IOException e) {
889 throw new MarshalException(e);
890 }
891 } //-- marshal
892
893 /**
894 * Marshals the given Object as XML using the given DocumentHandler
895 * to send events to.
896 *
897 * @param object The Object to marshal.
898 * @param handler The DocumentHandler to marshal to.
899 * @exception org.exolab.castor.xml.MarshalException
900 * @exception org.exolab.castor.xml.ValidationException
901 */
902 public static void marshal(Object object, DocumentHandler handler)
903 throws MarshalException, ValidationException {
904 staticMarshal(object, new Marshaller(handler));
905 } //-- marshal
906
907 /**
908 * Marshals the given Object as XML using the given ContentHandler
909 * to send events to.
910 *
911 * @param object The Object to marshal.
912 * @param handler The ContentHandler to marshal to.
913 * @exception org.exolab.castor.xml.MarshalException
914 * @exception org.exolab.castor.xml.ValidationException
915 */
916 public static void marshal(Object object, ContentHandler handler)
917 throws MarshalException, ValidationException {
918 staticMarshal(object, new Marshaller(handler));
919 } //-- marshal
920
921 /**
922 * Marshals the given Object as XML using the given DOM Node
923 * to send events to.
924 *
925 * @param object The Object to marshal.
926 * @param node The DOM Node to marshal to.
927 * @exception org.exolab.castor.xml.MarshalException
928 * @exception org.exolab.castor.xml.ValidationException
929 */
930 public static void marshal(Object object, Node node)
931 throws MarshalException, ValidationException {
932 staticMarshal(object, new Marshaller(node));
933 } //-- marshal
934
935 /**
936 * Static helper method to marshal the given object using the
937 * Marshaller instance provided.
938 *
939 * @param object The Object to marshal.
940 * @param marshaller The {@link Marshaller} to use for marshalling.
941 * @throws MarshalException as thrown by marshal(Object)
942 * @throws ValidationException as thrown by marshal(Object)
943 */
944 private static void staticMarshal(final Object object, final Marshaller marshaller)
945 throws MarshalException, ValidationException {
946 if (object == null) {
947 throw new MarshalException("object must not be null");
948 }
949
950 if (LOG.isInfoEnabled()) {
951 LOG.info("Marshaller called using one of the *static* " +
952 " marshal(Object, *) methods. This will ignore any " +
953 " mapping files as specified. Please consider switching to " +
954 " using Marshaller instances and calling one of the" +
955 " marshal(*) methods.");
956 }
957
958 marshaller.marshal(object);
959 } //-- staticMarshal
960
961 /**
962 * Marshals the given Object as XML using the DocumentHandler
963 * for this Marshaller.
964 *
965 * @param object The Object to marshal.
966 * @exception org.exolab.castor.xml.MarshalException
967 * @exception org.exolab.castor.xml.ValidationException
968 */
969 public void marshal(Object object)
970 throws MarshalException, ValidationException {
971 if (object == null)
972 throw new MarshalException("object must not be null");
973
974 if (LOG.isDebugEnabled()) {
975 LOG.debug("Marshalling " + object.getClass().getName());
976 }
977
978 if (object instanceof AnyNode) {
979 try{
980 AnyNode2SAX2.fireEvents((AnyNode)object, _handler, namespacesStack);
981 } catch(SAXException e) {
982 throw new MarshalException(e);
983 }
984 }
985 else {
986 validate(object);
987 MarshalState mstate = new MarshalState(object, "root");
988 if (_asDocument) {
989 try {
990 _handler.startDocument();
991 //-- handle processing instructions
992 for (int i = 0; i < _processingInstructions.size(); i++) {
993 ProcessingInstruction pi = _processingInstructions.get(i);
994 _handler.processingInstruction(pi.getTarget(),
995 pi.getData());
996 }
997 marshal(object, null, _handler, mstate);
998 _handler.endDocument();
999 } catch (SAXException sx) {
1000 throw new MarshalException(sx);
1001 }
1002 }
1003 else {
1004 marshal(object, null, _handler, mstate);
1005 }
1006 }
1007
1008 } //-- marshal
1009
1010 /**
1011 * Marshals the given object, using the given descriptor
1012 * and document handler.
1013 *
1014 * <BR/>
1015 * <B>Note:</B>
1016 * <I>
1017 * It is an error if this method is called with an
1018 * AttributeDescriptor.
1019 * </I>
1020 * @param descriptor the XMLFieldDescriptor for the given object
1021 * @param handler the DocumentHandler to marshal to
1022 * @exception org.exolab.castor.xml.MarshalException
1023 * @exception org.exolab.castor.xml.ValidationException
1024 * during marshaling
1025 **/
1026 private void marshal
1027 (Object object,
1028 XMLFieldDescriptor descriptor,
1029 ContentHandler handler,
1030 final MarshalState mstate)
1031 throws MarshalException, ValidationException
1032 {
1033
1034
1035 if (object == null) {
1036 String err = "Marshaller#marshal: null parameter: 'object'";
1037 throw new MarshalException(err);
1038 }
1039
1040 if (descriptor != null && descriptor.isTransient())
1041 return;
1042
1043 //-- notify listener
1044 if (_marshalListener != null) {
1045 boolean toBeMarshalled = true;
1046 try {
1047 toBeMarshalled = _marshalListener.preMarshal(object);
1048 } catch (RuntimeException e) {
1049 LOG.error("Invoking #preMarshal() on your custom MarshalListener instance caused the following problem:", e);
1050 }
1051 if (!toBeMarshalled) {
1052 return;
1053 }
1054 }
1055
1056 //-- handle AnyNode
1057 if (object instanceof AnyNode) {
1058 try {
1059 AnyNode2SAX2.fireEvents((AnyNode) object, handler, namespacesStack);
1060 }catch (SAXException e) {
1061 throw new MarshalException(e);
1062 }
1063 return;
1064 }
1065
1066 boolean containerField = false;
1067
1068 if (descriptor != null && descriptor.isContainer()) {
1069 containerField = true;
1070 }
1071
1072 //-- add object to stack so we don't potentially get into
1073 //-- an endlessloop
1074 if (_parents.search(object) >= 0) {
1075 return;
1076 }
1077
1078 _parents.push(object);
1079
1080 final boolean isNil = (object instanceof NilObject);
1081
1082 Class<?> cls = null;
1083
1084 if (!isNil) {
1085 cls = object.getClass();
1086
1087 if (_proxyInterfaces.size() > 0) {
1088 boolean isProxy = false;
1089
1090 Class<?>[] interfaces = cls.getInterfaces();
1091 for (int i = 0; i < interfaces.length; i++) {
1092 if (_proxyInterfaces.contains(interfaces[i].getName())) {
1093 isProxy = true;
1094 }
1095 }
1096
1097 if (isProxy) { cls = cls.getSuperclass(); }
1098 }
1099 } else {
1100 cls = ((NilObject) object).getClassDescriptor().getJavaClass();
1101 }
1102
1103 boolean byteArray = false;
1104 if (cls.isArray())
1105 byteArray = (cls.getComponentType() == Byte.TYPE);
1106
1107 boolean atRoot = false;
1108 if (descriptor == null) {
1109 descriptor = new XMLFieldDescriptorImpl(cls, "root", null, null);
1110 atRoot = true;
1111 }
1112
1113
1114 //-- calculate Object's name
1115 String name = descriptor.getXMLName();
1116 if (atRoot && _rootElement!=null)
1117 name = _rootElement;
1118
1119 boolean autoNameByClass = false;
1120 if (name == null) {
1121 autoNameByClass = true;
1122 name = cls.getName();
1123 //-- remove package information from name
1124 int idx = name.lastIndexOf('.');
1125 if (idx >= 0) {
1126 name = name.substring(idx+1);
1127 }
1128 //-- remove capitalization
1129 name = getInternalContext().getXMLNaming().toXMLName(name);
1130 }
1131
1132 //-- obtain the class descriptor
1133 XMLClassDescriptor classDesc = null;
1134 boolean saveType = false; /* flag for xsi:type */
1135
1136 if (object instanceof NilObject) {
1137 classDesc = ((NilObject)object).getClassDescriptor();
1138 } else if (cls == descriptor.getFieldType()) {
1139 classDesc = (XMLClassDescriptor)descriptor.getClassDescriptor();
1140 }
1141
1142 if (classDesc == null) {
1143
1144 //-- check for primitive or String, we need to use
1145 //-- the special #isPrimitive method of this class
1146 //-- so that we can check for the primitive wrapper
1147 //-- classes
1148 if (isPrimitive(cls) || byteArray) {
1149 classDesc = STRING_CLASS_DESCRIPTOR;
1150 //-- check to see if we need to save the xsi:type
1151 //-- for this class
1152 Class<?> fieldType = descriptor.getFieldType();
1153 if (cls != fieldType) {
1154 while (fieldType.isArray()) {
1155 fieldType = fieldType.getComponentType();
1156 }
1157 saveType = (!primitiveOrWrapperEquals(cls, fieldType));
1158 }
1159 }
1160 else {
1161 saveType = cls.isArray();
1162 //-- save package information for use when searching
1163 //-- for MarshalInfo classes
1164 String className = cls.getName();
1165 int idx = className.lastIndexOf(".");
1166 String pkgName = null;
1167 if (idx > 0) {
1168 pkgName = className.substring(0,idx+1);
1169 if (!_packages.contains(pkgName))
1170 _packages.add(pkgName);
1171 }
1172
1173 if (_marshalExtendedType) {
1174 //-- Check to see if we can determine the class or
1175 //-- ClassDescriptor from the type specified in the
1176 //-- FieldHandler or from the current CDR state
1177
1178 if ((cls != descriptor.getFieldType()) || atRoot) {
1179
1180 saveType = true;
1181
1182 boolean containsDesc = false;
1183
1184 //-- if we're not at the root, check to see if we can resolve name.
1185 //-- if we're at the root, the name will most likely be resolvable
1186 //-- due to the validation step, so in most cases, if we are not
1187 //-- using a mapping we need the xsi:type at the root
1188 if (!atRoot) {
1189 String nsURI = descriptor.getNameSpaceURI();
1190 XMLClassDescriptor tmpDesc = null;
1191 try {
1192 tmpDesc = getResolver().resolveByXMLName(name, nsURI, null);
1193 }
1194 catch(ResolverException rx) {
1195 //-- exception not important as we're simply
1196 //-- testing to see if we can resolve during
1197 //-- unmarshalling
1198 if (LOG.isDebugEnabled()) {
1199 LOG.debug("Error resolving", rx);
1200 }
1201 }
1202
1203 if (tmpDesc != null) {
1204 Class<?> tmpType = tmpDesc.getJavaClass();
1205 if (tmpType == cls) {
1206 containsDesc = (!tmpType.isInterface());
1207 }
1208 }
1209 }
1210
1211 if (!containsDesc) {
1212 //-- check for class mapping, we don't use the
1213 //-- resolver directly because it will try to
1214 //-- load a compiled descriptor, or introspect
1215 //-- one
1216 if (atRoot) {
1217 if (_useXSITypeAtRoot) {
1218 XMLMappingLoader ml = (XMLMappingLoader) getResolver().getMappingLoader();
1219 if (ml != null) {
1220 containsDesc = (ml.getDescriptor(cls.getName()) != null);
1221 }
1222 }
1223 else {
1224 //-- prevent xsi:type from appearing
1225 //-- on root
1226 containsDesc = true;
1227 }
1228
1229 }
1230
1231 //-- The following logic needs to be expanded to use
1232 //-- namespace -to- package mappings
1233 if ((!containsDesc) && (pkgName == null)) {
1234 //-- check to see if the class name is guessable
1235 //-- from the xml name
1236 classDesc = getClassDescriptor(cls);
1237 if (classDesc != null) {
1238 String tmpName = classDesc.getXMLName();
1239 if (name.equals(tmpName))
1240 saveType = false;
1241 }
1242 }
1243 }
1244
1245 if (containsDesc) saveType = false;
1246
1247 }
1248
1249 // marshal as the actual type
1250 if (classDesc == null)
1251 classDesc = getClassDescriptor(cls);
1252
1253 } //-- end if (marshalExtendedType)
1254 else {
1255 // marshall as the base field type
1256 cls = descriptor.getFieldType();
1257 classDesc = getClassDescriptor(cls);
1258 }
1259
1260 //-- If we are marshalling an array as the top
1261 //-- level object, or if we run into a multi
1262 //-- dimensional array, use the special
1263 //-- ArrayDescriptor
1264 if ((classDesc == null) && cls.isArray()) {
1265 classDesc = new RootArrayDescriptor(cls);
1266 if (atRoot) {
1267 containerField = (!_asDocument);
1268 }
1269 }
1270 } //-- end else not primitive
1271
1272 if (classDesc == null) {
1273 //-- make sure we are allowed to marshal Object
1274 if ((cls == Void.class) ||
1275 (cls == Object.class) ||
1276 (cls == Class.class)) {
1277
1278 throw new MarshalException
1279 (MarshalException.BASE_CLASS_OR_VOID_ERR);
1280 }
1281 _parents.pop();
1282 return;
1283 }
1284 }
1285
1286 //-- handle auto-naming by class
1287 if (autoNameByClass) {
1288 if (classDesc.getXMLName() != null) {
1289 name = classDesc.getXMLName();
1290 }
1291 }
1292
1293 //-- at this point naming should be done, update
1294 //-- MarshalState.xmlName if root element
1295 if (atRoot) {
1296 mstate._xmlName = name;
1297 }
1298
1299 //------------------------------------------------/
1300 //- Next few sections of code deal with xsi:type -/
1301 //- prevention, if necessary -/
1302 //------------------------------------------------/
1303
1304 //-- Allow user to prevent xsi:type
1305 saveType = (saveType && (!_suppressXSIType));
1306
1307 //-- Suppress xsi:type for special types
1308 if (saveType) {
1309 //-- java.util.Enumeration and java.util.Date fix
1310 if (descriptor.getHandler() instanceof DateFieldHandler)
1311 saveType = false;
1312 else if (descriptor.getHandler() instanceof EnumFieldHandler)
1313 saveType = false;
1314 else if (isNil)
1315 saveType = false;
1316 }
1317
1318 //-- Suppress 'xsi:type' attributes when Castor is able to infer
1319 //-- the correct type during unmarshalling
1320 if (saveType) {
1321 // When the type of the instance of the field is not the
1322 // type specified for the field, it might be necessary to
1323 // store the type explicitly (using xsi:type) to avoid
1324 // confusion during unmarshalling.
1325 //
1326 // However, it might be possible to use the XMLName of the
1327 // instance rather than the XMLName of the field. If
1328 // Castor could find back the type from the name of the
1329 // element, there is no need to add an xsi:type.
1330 //
1331 // In order to do that, there is two conditions:
1332 // 1. Castor should be able to find the right class to
1333 // instantiate form the XMLName of the instance.
1334 // 2. Castor should be sure than when using the XMLName of
1335 // the instance, there is only one field wich will match
1336 // that name (and that it is the current field)
1337
1338 // XML Name associated with the class we are marshalling
1339 String xmlElementName = name;
1340 String xmlNamespace = descriptor.getNameSpaceURI();
1341
1342 // We try to find if there is a XMLClassDescriptor associated
1343 // with the XML name of this class
1344 XMLClassDescriptor xmlElementNameClassDesc = null;
1345 try {
1346 xmlElementNameClassDesc = getResolver().resolveByXMLName(xmlElementName, null, null);
1347 }
1348 catch(ResolverException rx) {
1349 //-- exception not important as we're simply
1350 //-- testing to see if we can resolve during
1351 //-- unmarshalling
1352 if (LOG.isDebugEnabled()) {
1353 LOG.debug("Error resolving " + xmlElementName, rx);
1354 }
1355 }
1356
1357 // Test if we are not dealing with a source generated vector
1358 if ((xmlElementName != null) && (xmlElementNameClassDesc != null)) {
1359 // More than one class can map to a given element name
1360 try {
1361 Iterator<ClassDescriptor> classDescriptorIter = getResolver().resolveAllByXMLName(xmlElementName, null, null);
1362 for (; classDescriptorIter.hasNext();) {
1363 xmlElementNameClassDesc = (XMLClassDescriptor) classDescriptorIter.next();
1364 if (cls == xmlElementNameClassDesc.getJavaClass()) {
1365 break;
1366 }
1367 //reset the classDescriptor --> none has been found
1368 xmlElementNameClassDesc = null;
1369 }
1370 }
1371 catch(ResolverException rx) {
1372 if (LOG.isDebugEnabled()) {
1373 LOG.debug("Error resolving " + xmlElementName, rx);
1374 }
1375 xmlElementNameClassDesc = null;
1376 }
1377
1378 //make sure we only run into this logic if the classDescriptor
1379 //is coming from a mapping file.
1380 if (xmlElementNameClassDesc instanceof XMLClassDescriptorAdapter) {
1381
1382 // Try to find a field descriptor directly in the parent object
1383 XMLClassDescriptor tempContaining = (XMLClassDescriptor)descriptor.getContainingClassDescriptor();
1384
1385 //--if no containing class descriptor
1386 //--it means the container class could have been introspected
1387 //--so no need to enter the logic
1388 if (tempContaining != null) {
1389 XMLFieldDescriptor fieldDescMatch =
1390 tempContaining.getFieldDescriptor(xmlElementName, xmlNamespace, NodeType.Element);
1391
1392 // Try to find a field descriptor by inheritance in the parent object
1393 InheritanceMatch[] matches =
1394 searchInheritance(xmlElementName, null, tempContaining); // TODO: Joachim, _cdResolver);
1395
1396 if (matches.length == 1) {
1397
1398 boolean foundTheRightClass = ((xmlElementNameClassDesc != null) && (cls == xmlElementNameClassDesc.getJavaClass()));
1399
1400 boolean oneAndOnlyOneMatchedField
1401 = ((fieldDescMatch != null) ||
1402 (matches[0].parentFieldDesc == descriptor));
1403
1404 // Can we remove the xsi:type ?
1405 if (foundTheRightClass && oneAndOnlyOneMatchedField) {
1406 saveType = false;
1407 //no name swapping for now
1408 }
1409 }//lengh is one
1410 }
1411 }//the classDesc comes from a mapping file
1412 }
1413 }//--- End of "if (saveType)"
1414
1415
1416 //------------------------/
1417 //- Namespace Management -/
1418 //------------------------/
1419
1420 //-- Set a new namespace scoping
1421 //-- Note: We still need to declare a new scope even if
1422 //-- we are suppressing most namespaces. Certain elements
1423 //-- like xsi:type and xsi:nil will require a namespace
1424 //-- declaration and cannot be suppressed.
1425 if (!atRoot) {
1426 namespacesStack.addNewNamespaceScope();
1427 }
1428
1429 String nsPrefix = "";
1430 String nsURI = "";
1431
1432 if (!_suppressNamespaces) {
1433
1434 //-- Must be done before any attributes are processed
1435 //-- since attributes can be namespaced as well.
1436
1437 nsPrefix = descriptor.getNameSpacePrefix();
1438 if (nsPrefix == null) nsPrefix = classDesc.getNameSpacePrefix();
1439
1440 nsURI = descriptor.getNameSpaceURI();
1441 if (nsURI == null) nsURI = classDesc.getNameSpaceURI();
1442
1443 if ((nsURI == null) && (nsPrefix != null)) {
1444 nsURI = namespacesStack.getNamespaceURI(nsPrefix);
1445 }
1446 else if ((nsPrefix == null) && (nsURI != null)) {
1447 nsPrefix = namespacesStack.getNamespacePrefix(nsURI);
1448 }
1449 //-- declare namespace at this element scope?
1450 if (nsURI != null) {
1451 String defaultNamespace = namespacesStack.getDefaultNamespaceURI();
1452 if ((nsPrefix == null) && (!nsURI.equals(defaultNamespace)))
1453 {
1454 if ((defaultNamespace == null) && atRoot) {
1455 nsPrefix = "";
1456 }
1457 else nsPrefix = DEFAULT_PREFIX + (++_namespaceCounter);
1458 }
1459 declareNamespace(nsPrefix, nsURI);
1460 }
1461 else {
1462 nsURI = "";
1463 //-- redeclare default namespace as empty
1464 String defaultNamespace = namespacesStack.getNamespaceURI("");
1465 if ((defaultNamespace != null) && (!"".equals(defaultNamespace)))
1466 namespacesStack.addNamespace("", "");
1467 }
1468 }
1469
1470
1471
1472 //---------------------/
1473 //- handle attributes -/
1474 //---------------------/
1475
1476 AttributesImpl atts = new AttributesImpl();
1477
1478 //-- user defined attributes
1479 if (atRoot) {
1480 //-- declare xsi prefix if necessary
1481 if (_topLevelAtts.getSize() > 0) {
1482 namespacesStack.addNamespace(XSI_PREFIX, XSI_NAMESPACE);
1483 }
1484
1485 for (int i = 0; i < _topLevelAtts.getSize(); i++) {
1486 String localName = _topLevelAtts.getName(i);
1487 String qName = localName;
1488 String ns = "";
1489 if (!_suppressNamespaces) {
1490 ns = _topLevelAtts.getNamespace(i);
1491 String prefix = null;
1492 if (StringUtils.isNotEmpty(ns)) {
1493 prefix = namespacesStack.getNonDefaultNamespacePrefix(ns);
1494 }
1495 if (StringUtils.isNotEmpty(prefix)) {
1496 qName = prefix + ':' + qName;
1497 }
1498 if (ns == null) ns = "";
1499 }
1500 atts.addAttribute(ns, localName, qName, CDATA,
1501 _topLevelAtts.getValue(i));
1502 }
1503 }
1504
1505 //----------------------------
1506 //-- process attr descriptors
1507 //----------------------------
1508
1509 int nestedAttCount = 0;
1510 XMLFieldDescriptor[] nestedAtts = null;
1511 XMLFieldDescriptor[] descriptors = null;
1512 if ((!descriptor.isReference()) && (!isNil)) {
1513 descriptors = classDesc.getAttributeDescriptors();
1514 }
1515 else {
1516 // references don't have attributes
1517 descriptors = NO_FIELD_DESCRIPTORS;
1518 }
1519
1520 for (int i = 0; i < descriptors.length; i++) {
1521 XMLFieldDescriptor attributeDescriptor = descriptors[i];
1522 if (attributeDescriptor == null) {
1523 continue;
1524 }
1525 String path = attributeDescriptor.getLocationPath();
1526 if (StringUtils.isNotEmpty(path)) {
1527 //-- save for later processing
1528 if (nestedAtts == null) {
1529 nestedAtts = new XMLFieldDescriptor[descriptors.length - i];
1530 }
1531 nestedAtts[nestedAttCount] = attributeDescriptor;
1532 nestedAttCount++;
1533 } else {
1534 processAttribute(object, attributeDescriptor, atts);
1535 }
1536 }
1537
1538 //-- handle ancestor nested attributes
1539 if (mstate._nestedAttCount > 0) {
1540 for (int i = 0; i < mstate._nestedAtts.length; i++) {
1541 XMLFieldDescriptor attributeDescriptor = mstate._nestedAtts[i];
1542 if (attributeDescriptor == null) {
1543 continue;
1544 }
1545 String locationPath = attributeDescriptor.getLocationPath();
1546 if (name.equals(locationPath)) {
1547 // indicate that this 'nested' attribute has been processed
1548 mstate._nestedAtts[i] = null;
1549 // decrease number of unprocessed 'nested' attributes by one
1550 mstate._nestedAttCount--;
1551 processAttribute(mstate.getOwner(), attributeDescriptor, atts);
1552 }
1553 }
1554 }
1555
1556 //-- Look for attributes in container fields,
1557 //-- (also handle container in container)
1558 if (!isNil) processContainerAttributes(object, classDesc, atts);
1559
1560 //-- xml:space
1561 String attValue = descriptor.getXMLProperty(XMLFieldDescriptor.PROPERTY_XML_SPACE);
1562 if (attValue != null) {
1563 atts.addAttribute(Namespaces.XML_NAMESPACE, SPACE_ATTR, XML_SPACE_ATTR, CDATA, attValue);
1564 }
1565
1566 //-- xml:lang
1567 attValue = descriptor.getXMLProperty(XMLFieldDescriptor.PROPERTY_XML_LANG);
1568 if (attValue != null) {
1569 atts.addAttribute(Namespaces.XML_NAMESPACE, LANG_ATTR, XML_LANG_ATTR, CDATA, attValue);
1570 }
1571
1572
1573 //------------------/
1574 //- Create element -/
1575 //------------------/
1576
1577
1578 //-- save xsi:type information, if necessary
1579
1580 if (saveType) {
1581 //-- declare XSI namespace, if necessary
1582 declareNamespace(XSI_PREFIX, XSI_NAMESPACE);
1583
1584 //-- calculate type name, either use class name or
1585 //-- schema type name. If XMLClassDescriptor is introspected,
1586 //-- or is the default XMLClassDescriptorImpl, then
1587 //-- use java:classname, otherwise use XML name.
1588 String typeName = classDesc.getXMLName();
1589
1590 //-- Check for introspection...
1591 boolean introspected = false;
1592 if (classDesc instanceof InternalXMLClassDescriptor)
1593 introspected = ((InternalXMLClassDescriptor)classDesc).introspected();
1594 else
1595 introspected = Introspector.introspected(classDesc);
1596
1597 boolean useJavaPrefix = false;
1598 if ((typeName == null) || introspected) {
1599 typeName = JAVA_PREFIX + cls.getName();
1600 useJavaPrefix = true;
1601 }
1602 else if (classDesc instanceof RootArrayDescriptor) {
1603 typeName = JAVA_PREFIX + cls.getName();
1604 useJavaPrefix = true;
1605 }
1606 else {
1607 String dcn = classDesc.getClass().getName();
1608 if (dcn.equals(XMLClassDescriptorImpl.class.getName())) {
1609 typeName = JAVA_PREFIX + cls.getName();
1610 useJavaPrefix = true;
1611 }
1612 else {
1613 //-- calculate proper prefix
1614 String tns = classDesc.getNameSpaceURI();
1615 String prefix = null;
1616 if (StringUtils.isNotEmpty(tns)) {
1617 prefix = namespacesStack.getNamespacePrefix(tns);
1618 if (StringUtils.isNotEmpty(prefix)) {
1619 typeName = prefix + ':' + typeName;
1620 }
1621 }
1622 }
1623 }
1624 //-- save type information
1625 atts.addAttribute(XSI_NAMESPACE, TYPE_ATTR, XSI_TYPE, CDATA, typeName);
1626 if (useJavaPrefix) {
1627 if (namespacesStack.getNamespaceURI("java") == null) {
1628 //-- declare Java namespace, if necessary
1629 declareNamespace("java", "http://java.sun.com");
1630 }
1631 }
1632 }
1633
1634 if (isNil && !_suppressXSIType) {
1635 //-- declare XSI namespace, if necessary
1636 declareNamespace(XSI_PREFIX, XSI_NAMESPACE);
1637 //-- add xsi:nil="true"
1638 atts.addAttribute(XSI_NAMESPACE, NIL_ATTR, XSI_NIL_ATTR, CDATA, TRUE_VALUE);
1639 }
1640
1641 //check if the value is a QName that needs to
1642 //be resolved ({URI}value -> ns:value)
1643 //This should be done BEFORE declaring the namespaces as attributes
1644 //because we can declare new namespace during the QName resolution
1645 String valueType = descriptor.getSchemaType();
1646 if ((valueType != null) && (valueType.equals(QNAME_NAME))) {
1647 object = resolveQName(object, descriptor);
1648 }
1649
1650
1651 String qName = null;
1652 if (nsPrefix != null) {
1653 int len = nsPrefix.length();
1654 if (len > 0) {
1655 StringBuffer sb = new StringBuffer(len+name.length()+1);
1656 sb.append(nsPrefix);
1657 sb.append(':');
1658 sb.append(name);
1659 qName = sb.toString();
1660 }
1661 else qName = name;
1662 }
1663 else qName = name;
1664
1665
1666 Object firstNonNullValue = null;
1667 int firstNonNullIdx = 0;
1668
1669 try {
1670
1671
1672 if (!containerField) {
1673
1674
1675 //-- isNillable?
1676 if ((!isNil) && descriptor.isNillable()) {
1677 XMLFieldDescriptor desc = classDesc.getContentDescriptor();
1678 descriptors = classDesc.getElementDescriptors();
1679 int descCount = descriptors.length;
1680 boolean isNilContent = (descCount > 0) || (desc != null);
1681
1682 //-- check content descriptor for a valid value
1683 if (desc != null) {
1684 Object value = desc.getHandler().getValue(object);
1685 if (value != null) {
1686 isNilContent = false;
1687 descCount = 0;
1688 }
1689 else if (desc.isNillable() && desc.isRequired()) {
1690 isNilContent = false;
1691 descCount = 0;
1692 }
1693 }
1694
1695
1696 for (int i = 0; i < descCount; i++) {
1697 desc = descriptors[i];
1698 if (desc == null) continue;
1699 Object value = desc.getHandler().getValue(object);
1700 if (value != null) {
1701 isNilContent = false;
1702 firstNonNullIdx = i;
1703 firstNonNullValue = value;
1704 break;
1705 }
1706 else if (desc.isNillable() && desc.isRequired()) {
1707 isNilContent = false;
1708 firstNonNullIdx = i;
1709 firstNonNullValue = new NilObject(classDesc, desc);
1710 break;
1711 }
1712 }
1713
1714 if (isNilContent) {
1715 declareNamespace(XSI_PREFIX, XSI_NAMESPACE);
1716 atts.addAttribute(XSI_NAMESPACE, NIL_ATTR, XSI_NIL_ATTR, CDATA, TRUE_VALUE);
1717 }
1718 }
1719
1720 //-- declare all necesssary namespaces
1721 namespacesStack.getCurrentNamespaceScope().sendStartEvents(handler);
1722 //-- Make sure qName is not null
1723 if (qName == null) {
1724 //-- hopefully this never happens, but if it does, it means
1725 //-- we have a bug in our naming logic
1726 String err = "Error in deriving name for type: " +
1727 cls.getName() + ", please report bug to: " +
1728 "http://castor.exolab.org.";
1729 throw new IllegalStateException(err);
1730 }
1731
1732
1733 handler.startElement(nsURI, name, qName, atts);
1734 }
1735 }
1736 catch (org.xml.sax.SAXException sx) {
1737 throw new MarshalException(sx);
1738 }
1739
1740
1741 //---------------------------------------
1742 //-- process all child content, including
1743 //-- text nodes + daughter elements
1744 //---------------------------------------
1745
1746 Stack<WrapperInfo> wrappers = null;
1747
1748
1749 //----------------------
1750 //-- handle text content
1751 //----------------------
1752
1753 if (!isNil) {
1754
1755 XMLFieldDescriptor cdesc = null;
1756 if (!descriptor.isReference()) {
1757 cdesc = classDesc.getContentDescriptor();
1758 }
1759 if (cdesc != null) {
1760 Object obj = null;
1761 try {
1762 obj = cdesc.getHandler().getValue(object);
1763 }
1764 catch(IllegalStateException ise) {
1765 LOG.warn("Error getting value from: " + object, ise);
1766 }
1767
1768 if (obj != null) {
1769
1770 //-- <Wrapper>
1771 //-- handle XML path
1772 String path = cdesc.getLocationPath();
1773 String currentLoc = null;
1774
1775 if (path != null) {
1776 _attributes.clear();
1777 if (wrappers == null) {
1778 wrappers = new SafeStack<WrapperInfo>();
1779 }
1780 try {
1781 while (path != null) {
1782
1783 String elemName = null;
1784 int idx = path.indexOf('/');
1785
1786 if (idx > 0) {
1787 elemName = path.substring(0, idx);
1788 path = path.substring(idx+1);
1789 }
1790 else {
1791 elemName = path;
1792 path = null;
1793 }
1794
1795 //-- save current location without namespace
1796 //-- information for now.
1797 if (currentLoc == null)
1798 currentLoc = elemName;
1799 else
1800 currentLoc = currentLoc + "/" + elemName;
1801
1802 String elemQName = elemName;
1803 if (StringUtils.isNotEmpty(nsPrefix)) {
1804 elemQName = nsPrefix + ':' + elemName;
1805 }
1806 wrappers.push(new WrapperInfo(elemName, elemQName, currentLoc));
1807
1808
1809 _attributes.clear();
1810 if (nestedAttCount > 0) {
1811 for (int na = 0; na < nestedAtts.length; na++) {
1812 if (nestedAtts[na] == null) continue;
1813 String tmpPath = nestedAtts[na].getLocationPath();
1814 if (tmpPath.equals(currentLoc)) {
1815 processAttribute(object, nestedAtts[na],_attributes);
1816 nestedAtts[na] = null;
1817 --nestedAttCount;
1818 }
1819 }
1820 }
1821 handler.startElement(nsURI, elemName, elemQName, _attributes);
1822 }
1823 }
1824 catch(SAXException sx) {
1825 throw new MarshalException(sx);
1826 }
1827 }
1828 //-- </Wrapper>
1829
1830 char[] chars = null;
1831 Class<?> objType = obj.getClass();
1832 if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) {
1833 //-- handle base64/hexbinary content
1834 final String schemaType = descriptor.getSchemaType();
1835 if (HexDecoder.DATA_TYPE.equals(schemaType)) {
1836 chars = new String(HexDecoder.encode((byte[]) obj)).toCharArray();
1837 } else {
1838 chars = Base64Encoder.encode((byte[]) obj);
1839 }
1840 } else {
1841 //-- all other types
1842 String str = obj.toString();
1843 if (StringUtils.isNotEmpty(str)) {
1844 chars = str.toCharArray();
1845 }
1846 }
1847 if ((chars != null) && (chars.length > 0)) {
1848 try {
1849 handler.characters(chars, 0, chars.length);
1850 }
1851 catch(org.xml.sax.SAXException sx) {
1852 throw new MarshalException(sx);
1853 }
1854 }
1855 }
1856 }
1857 //-- element references
1858 else if (descriptor.isReference()) {
1859 Object id = getObjectID(object);
1860 if (id != null) {
1861 char[] chars = id.toString().toCharArray();
1862 try {
1863 handler.characters(chars, 0, chars.length);
1864 }
1865 catch(org.xml.sax.SAXException sx) {
1866 throw new MarshalException(sx);
1867 }
1868 }
1869 }
1870 // special case for byte[]
1871 else if (byteArray) {
1872 //-- Base64Encoding / HexBinary
1873 String schemaType = descriptor.getSchemaType();
1874 String componentType = descriptor.getComponentType();
1875 char[] chars = new char[0];
1876 if ((descriptor.isMultivalued() && HexDecoder.DATA_TYPE.equals(componentType)) ||
1877 HexDecoder.DATA_TYPE.equals(schemaType)) {
1878 chars = new String(HexDecoder.encode((byte[]) object)).toCharArray();
1879 } else {
1880 chars = Base64Encoder.encode((byte[]) object);
1881 }
1882 try {
1883 handler.characters(chars, 0, chars.length);
1884 } catch (org.xml.sax.SAXException sx) {
1885 throw new MarshalException(sx);
1886 }
1887 }
1888 /* special case for Strings and primitives */
1889 else if (isPrimitive(cls)) {
1890
1891 char[] chars;
1892 if (cls == java.math.BigDecimal.class) {
1893 chars = convertBigDecimalToString(object).toCharArray();
1894 } else {
1895 chars = object.toString().toCharArray();
1896 }
1897 try {
1898 handler.characters(chars,0,chars.length);
1899 }
1900 catch(org.xml.sax.SAXException sx) {
1901 throw new MarshalException(sx);
1902 }
1903 }
1904 else if (isEnum(cls)) {
1905 char[] chars = object.toString().toCharArray();
1906 try {
1907 handler.characters(chars,0,chars.length);
1908 }
1909 catch(org.xml.sax.SAXException sx) {
1910 throw new MarshalException(sx);
1911 }
1912
1913 }
1914 }
1915
1916 //---------------------------
1917 //-- handle daughter elements
1918 //---------------------------
1919
1920 if (isNil || descriptor.isReference()) {
1921 descriptors = NO_FIELD_DESCRIPTORS;
1922 }
1923 else {
1924 descriptors = classDesc.getElementDescriptors();
1925 }
1926
1927 ++_depth;
1928
1929 //-- marshal elements
1930 for (int i = firstNonNullIdx; i < descriptors.length; i++) {
1931
1932 XMLFieldDescriptor elemDescriptor = descriptors[i];
1933 Object obj = null;
1934 boolean nil = false;
1935
1936 //-- used previously cached value?
1937 if ((i == firstNonNullIdx) && (firstNonNullValue != null)) {
1938 obj = firstNonNullValue;
1939 } else {
1940 //-- obtain value from handler
1941 try {
1942 obj = elemDescriptor.getHandler().getValue(object);
1943 } catch (IllegalStateException ise) {
1944 LOG.warn("Error marshalling " + object, ise);
1945 continue;
1946 }
1947 }
1948
1949 if (obj == null
1950 || (obj instanceof Enumeration && !((Enumeration<?>) obj).hasMoreElements())) {
1951 if (elemDescriptor.isNillable() && (elemDescriptor.isRequired())) {
1952 nil = true;
1953 } else {
1954 continue;
1955 }
1956 }
1957
1958
1959 //-- handle XML path
1960 String path = elemDescriptor.getLocationPath();
1961 String currentLoc = null;
1962 //-- Wrapper/Location cleanup
1963 if (wrappers != null) {
1964 try {
1965 while (!wrappers.empty()) {
1966 WrapperInfo wInfo = wrappers.peek();
1967 if (path != null) {
1968 if (wInfo._location.equals(path)) {
1969 path = null;
1970 break;
1971 }
1972 else if (path.startsWith(wInfo._location + "/")) {
1973 path = path.substring(wInfo._location.length()+1);
1974 currentLoc = wInfo._location;
1975 break;
1976 }
1977 }
1978 handler.endElement(nsURI, wInfo._localName, wInfo._qName);
1979 wrappers.pop();
1980 }
1981 }
1982 catch(SAXException sx) {
1983 throw new MarshalException(sx);
1984 }
1985 }
1986
1987
1988 if (path != null) {
1989 _attributes.clear();
1990 if (wrappers == null) {
1991 wrappers = new SafeStack<WrapperInfo>();
1992 }
1993 try {
1994 while (path != null) {
1995
1996 String elemName = null;
1997 int idx = path.indexOf('/');
1998
1999 if (idx > 0) {
2000 elemName = path.substring(0, idx);
2001 path = path.substring(idx+1);
2002 }
2003 else {
2004 elemName = path;
2005 path = null;
2006 }
2007
2008 //-- save current location without namespace
2009 //-- information for now.
2010 if (currentLoc == null)
2011 currentLoc = elemName;
2012 else
2013 currentLoc = currentLoc + "/" + elemName;
2014
2015 String elemQName = elemName;
2016 if (StringUtils.isNotEmpty(nsPrefix)) {
2017 elemQName = nsPrefix + ':' + elemName;
2018 }
2019 wrappers.push(new WrapperInfo(elemName, elemQName, currentLoc));
2020
2021
2022 _attributes.clear();
2023 if (nestedAttCount > 0) {
2024 for (int na = 0; na < nestedAtts.length; na++) {
2025 if (nestedAtts[na] == null) continue;
2026 String tmpPath = nestedAtts[na].getLocationPath();
2027 if (tmpPath.equals(currentLoc)) {
2028 processAttribute(object, nestedAtts[na],_attributes);
2029 nestedAtts[na] = null;
2030 --nestedAttCount;
2031 }
2032 }
2033 }
2034 handler.startElement(nsURI, elemName, elemQName, _attributes);
2035 }
2036 }
2037 catch(SAXException sx) {
2038 throw new MarshalException(sx);
2039 }
2040 }
2041
2042 if (nil) {
2043 obj = new NilObject(classDesc, elemDescriptor);
2044 }
2045
2046 final Class<?> type = obj.getClass();
2047
2048 MarshalState myState = mstate.createMarshalState(object, name);
2049 myState._nestedAtts = nestedAtts;
2050 myState._nestedAttCount = nestedAttCount;
2051
2052
2053 //-- handle byte arrays
2054 if (type.isArray() && (type.getComponentType() == Byte.TYPE)) {
2055 marshal(obj, elemDescriptor, handler, myState);
2056 } else if (type.isArray() && elemDescriptor.isDerivedFromXSList()) {
2057 Object buffer = processXSListType(obj, elemDescriptor);
2058 String elemName = elemDescriptor.getXMLName();
2059 String elemQName = elemName;
2060 if (StringUtils.isNotEmpty(nsPrefix)) {
2061 elemQName = nsPrefix + ':' + elemName;
2062 }
2063 char[] chars = buffer.toString().toCharArray();
2064 try {
2065 handler.startElement(nsURI, elemName, elemQName, _attributes);
2066 handler.characters(chars,0,chars.length);
2067 handler.endElement(nsURI, elemName, elemQName);
2068 }
2069 catch(org.xml.sax.SAXException sx) {
2070 throw new MarshalException(sx);
2071 }
2072 }
2073 //-- handle all other collection types
2074 else if (isCollection(type)) {
2075 boolean processCollection = true;
2076 if (_saveMapKeys) {
2077 MapHandler mapHandler = MapHandlers.getHandler(type);
2078 if (mapHandler != null) {
2079 processCollection = false;
2080 MapItem item = new MapItem();
2081 Enumeration<?> keys = mapHandler.keys(obj);
2082 while (keys.hasMoreElements()) {
2083 item.setKey(keys.nextElement());
2084 item.setValue(mapHandler.get(obj, item.getKey()));
2085 marshal(item, elemDescriptor, handler, myState);
2086 }
2087 }
2088
2089 }
2090 if (processCollection) {
2091 CollectionHandler<?> colHandler = getCollectionHandler(type);
2092 Enumeration<?> enumeration = colHandler.elements(obj);
2093 while (enumeration.hasMoreElements()) {
2094 Object item = enumeration.nextElement();
2095 if (item != null) {
2096 marshal(item, elemDescriptor, handler, myState);
2097 }
2098 }
2099 }
2100 }
2101 //-- otherwise just marshal object as is
2102 else {
2103 marshal(obj, elemDescriptor, handler, myState);
2104 }
2105
2106 if (nestedAttCount > 0) {
2107 nestedAttCount = myState._nestedAttCount;
2108 }
2109
2110 }
2111
2112
2113 //-- Wrapper/Location cleanup for elements
2114 if (wrappers != null) {
2115 try {
2116 while (!wrappers.empty()) {
2117 WrapperInfo wInfo = wrappers.peek();
2118 boolean popStack = true;
2119 if (nestedAttCount > 0) {
2120 for (int na = 0; na < nestedAtts.length; na++) {
2121 // TODO[LL]: refactor to avoid check against null
2122 if (nestedAtts[na] == null) continue;
2123 String nestedAttributePath = nestedAtts[na].getLocationPath();
2124 if (nestedAttributePath.startsWith(wInfo._location + "/")) {
2125 popStack = false;
2126 break;
2127 }
2128 }
2129 }
2130 if (popStack) {
2131 handler.endElement(nsURI, wInfo._localName, wInfo._qName);
2132 wrappers.pop();
2133 } else {
2134 break;
2135 }
2136 }
2137 }
2138 catch(SAXException sx) {
2139 throw new MarshalException(sx);
2140 }
2141 }
2142
2143 // TODO: Handling additional attributes at the end causes elements to be marshalled in the wrong
2144 // order when element 'text' is null, but their attribute value is not null. Can this be fixed
2145 // to process element attributes even when the element text value is null?
2146
2147 if (wrappers != null && !wrappers.isEmpty()) {
2148 dealWithNestedAttributesNested(object, handler, nsPrefix, nsURI,
2149 nestedAttCount, nestedAtts, wrappers);
2150 }
2151
2152 dealWithNestedAttributes(object, handler, nsPrefix, nsURI,
2153 nestedAttCount, nestedAtts, new SafeStack<WrapperInfo>());
2154
2155 //-- finish element
2156 try {
2157 if (!containerField) {
2158 handler.endElement(nsURI, name, qName);
2159 //-- undeclare all necesssary namespaces
2160 namespacesStack.getCurrentNamespaceScope().sendEndEvents(handler);
2161 }
2162 }
2163 catch(org.xml.sax.SAXException sx) {
2164 throw new MarshalException(sx);
2165 }
2166
2167 --_depth;
2168 _parents.pop();
2169 if (!atRoot) {
2170 namespacesStack.removeNamespaceScope();
2171 }
2172
2173 //-- notify listener of post marshal
2174 if (_marshalListener != null) {
2175 try {
2176 _marshalListener.postMarshal(object);
2177 } catch (RuntimeException e) {
2178 LOG.error("Invoking #postMarshal() on your custom MarshalListener instance caused the following problem:", e);
2179 }
2180 }
2181
2182 }
2183
2184 private void dealWithNestedAttributes(Object object,
2185 ContentHandler handler, String nsPrefix, String nsURI,
2186 int nestedAttCount, XMLFieldDescriptor[] nestedAtts, Stack<WrapperInfo> wrappers)
2187 throws MarshalException {
2188 //-- Handle any additional attribute locations that were
2189 //-- not handled when dealing with wrapper elements
2190 if (nestedAttCount > 0) {
2191 for (int i = 0; i < nestedAtts.length; i++) {
2192 if (nestedAtts[i] == null) continue;
2193 String path = nestedAtts[i].getLocationPath();
2194 String currentLoc = null;
2195
2196
2197 //-- Make sure attribute has value before continuing
2198 //-- We really could use a FieldHandler#hasValue()
2199 //-- method (since sometimes getValue() methods may
2200 //-- be expensive and we don't always want to call it
2201 //-- multiple times)
2202 if (nestedAtts[i].getHandler().getValue(object) == null) {
2203 nestedAtts[i] = null;
2204 -- nestedAttCount;
2205 continue;
2206 }
2207 try {
2208 while (path != null) {
2209 int idx = path.indexOf('/');
2210 String elemName = null;
2211 if (idx > 0) {
2212 elemName = path.substring(0,idx);
2213 path = path.substring(idx+1);
2214 }
2215 else {
2216 elemName = path;
2217 path = null;
2218 }
2219 if (currentLoc == null)
2220 currentLoc = elemName;
2221 else
2222 currentLoc = currentLoc + "/" + elemName;
2223
2224 String elemQName = elemName;
2225 if (StringUtils.isNotEmpty(nsPrefix)) {
2226 elemQName = nsPrefix + ':' + elemName;
2227 }
2228 wrappers.push(new WrapperInfo(elemName, elemQName, null));
2229
2230 _attributes.clear();
2231 if (path == null) {
2232 processAttribute(object, nestedAtts[i],_attributes);
2233 nestedAtts[i] = null;
2234 --nestedAttCount;
2235 }
2236 if (nestedAttCount > 0) {
2237 for (int na = i+1; na < nestedAtts.length; na++) {
2238 if (nestedAtts[na] == null) continue;
2239 String tmpPath = nestedAtts[na].getLocationPath();
2240 if (tmpPath.equals(currentLoc)) {
2241 processAttribute(object, nestedAtts[na],_attributes);
2242 nestedAtts[na] = null;
2243 --nestedAttCount;
2244 }
2245 }
2246 }
2247 handler.startElement(nsURI, elemName, elemQName, _attributes);
2248 }
2249
2250 while (!wrappers.empty()) {
2251 WrapperInfo wInfo = wrappers.pop();
2252 handler.endElement(nsURI, wInfo._localName, wInfo._qName);
2253 }
2254 } catch (Exception e) {
2255 throw new MarshalException(e);
2256 }
2257 }
2258 } // if (nestedAttCount > 0)
2259 }
2260
2261 private void dealWithNestedAttributesNested(Object object,
2262 ContentHandler handler, String nsPrefix, String nsURI,
2263 int nestedAttCount, XMLFieldDescriptor[] nestedAtts, Stack<WrapperInfo> wrappers)
2264 throws MarshalException {
2265 //-- Handle any additional attribute locations that were
2266 //-- not handled when dealing with wrapper elements
2267
2268 WrapperInfo wrapperInfo = wrappers.peek();
2269 String currentLocation = wrapperInfo._location;
2270
2271 if (nestedAttCount > 0) {
2272 for (int i = 0; i < nestedAtts.length; i++) {
2273 if (nestedAtts[i] == null) continue;
2274 String nestedAttributePath = nestedAtts[i].getLocationPath();
2275
2276 if (!nestedAttributePath.startsWith(currentLocation + "/")) {
2277 continue;
2278 }
2279
2280 nestedAttributePath = nestedAttributePath.substring(wrapperInfo._location.length() + 1);
2281 String currentLoc = currentLocation;
2282
2283
2284 //-- Make sure attribute has value before continuing
2285 //-- We really could use a FieldHandler#hasValue()
2286 //-- method (since sometimes getValue() methods may
2287 //-- be expensive and we don't always want to call it
2288 //-- multiple times)
2289 if (nestedAtts[i].getHandler().getValue(object) == null) {
2290 nestedAtts[i] = null;
2291 -- nestedAttCount;
2292 continue;
2293 }
2294 try {
2295 while (nestedAttributePath != null) {
2296 int idx = nestedAttributePath.indexOf('/');
2297 String elemName = null;
2298 if (idx > 0) {
2299 elemName = nestedAttributePath.substring(0,idx);
2300 nestedAttributePath = nestedAttributePath.substring(idx+1);
2301 }
2302 else {
2303 elemName = nestedAttributePath;
2304 nestedAttributePath = null;
2305 }
2306 if (currentLoc == null)
2307 currentLoc = elemName;
2308 else
2309 currentLoc = currentLoc + "/" + elemName;
2310
2311 String elemQName = elemName;
2312 if (StringUtils.isNotEmpty(nsPrefix)) {
2313 elemQName = nsPrefix + ':' + elemName;
2314 }
2315 wrappers.push(new WrapperInfo(elemName, elemQName, null));
2316
2317 _attributes.clear();
2318 if (nestedAttributePath == null) {
2319 processAttribute(object, nestedAtts[i],_attributes);
2320 nestedAtts[i] = null;
2321 --nestedAttCount;
2322 }
2323 if (nestedAttCount > 0) {
2324 for (int na = i+1; na < nestedAtts.length; na++) {
2325 if (nestedAtts[na] == null) continue;
2326 String tmpPath = nestedAtts[na].getLocationPath();
2327 if (tmpPath.equals(currentLoc)) {
2328 processAttribute(object, nestedAtts[na],_attributes);
2329 nestedAtts[na] = null;
2330 --nestedAttCount;
2331 }
2332 }
2333 }
2334 handler.startElement(nsURI, elemName, elemQName, _attributes);
2335 }
2336
2337 while (!wrappers.empty()) {
2338 WrapperInfo wInfo = wrappers.pop();
2339 handler.endElement(nsURI, wInfo._localName, wInfo._qName);
2340 }
2341 } catch (Exception e) {
2342 throw new MarshalException(e);
2343 }
2344 }
2345 } // if (nestedAttCount > 0)
2346 }
2347
2348
2349 /**
2350 * Converts a {@link BigDecimal} instance value to its String representation. This
2351 * method will take into into account the Java version number, as the semantics of
2352 * BigDecimal.toString() have changed between Java 1.4 and Java 5.0 and above.
2353 * @param object The {@link BigDecimal} instance
2354 * @return The String representation of the {@link BigDecimal} instance
2355 * @throws MarshalException If invocation of BigDecimal#toPlainString() fails.
2356 */
2357 private String convertBigDecimalToString(Object object) throws MarshalException {
2358 String stringValue;
2359 float javaVersion = Float.parseFloat(System.getProperty("java.specification.version"));
2360 if (javaVersion >= 1.5) {
2361 // as of Java 5.0 and above, BigDecimal.toPlainString() should be used.
2362 // TODO: reconsider if we start using BigDecimal for XSTypes that can hold scientific values
2363 Method method;
2364 try {
2365 method = java.math.BigDecimal.class.getMethod("toPlainString", (Class[]) null);
2366 stringValue = (String) method.invoke(object, (Object[]) null);
2367 } catch (Exception e) {
2368 LOG.error("Problem accessing java.math.BigDecimal.toPlainString().", e);
2369 throw new MarshalException("Problem accessing java.math.BigDecimal.toPlainString().", e);
2370 }
2371 } else {
2372 // use BigDecimal.toString() with Java 1.4 and below
2373 stringValue = object.toString();
2374 }
2375 return stringValue;
2376 }
2377
2378 /**
2379 * Retrieves the ID for the given Object
2380 *
2381 * @param object the Object to retrieve the ID for
2382 * @return the ID for the given Object
2383 **/
2384 private Object getObjectID(Object object)
2385 throws MarshalException
2386 {
2387 if (object == null) return null;
2388
2389 Object id = null;
2390 XMLClassDescriptor cd = getClassDescriptor(object.getClass());
2391 String err = null;
2392 if (cd != null) {
2393 XMLFieldDescriptor fieldDesc
2394 = (XMLFieldDescriptor) cd.getIdentity();
2395 if (fieldDesc != null) {
2396 FieldHandler<?> fieldHandler = fieldDesc.getHandler();
2397 if (fieldHandler != null) {
2398 try {
2399 id = fieldHandler.getValue(object);
2400 }
2401 catch(IllegalStateException ise) {
2402 err = ise.toString();
2403 }
2404 }//fieldHandler != null
2405 else {
2406 err = "FieldHandler for Identity descriptor is null.";
2407 }
2408 }//fieldDesc != null
2409 else err = "No identity descriptor available";
2410 }//cd!=null
2411 else {
2412 err = "Unable to resolve ClassDescriptor.";
2413 }
2414 if (err != null) {
2415 String errMsg = "Unable to resolve ID for instance of class '";
2416 errMsg += object.getClass().getName();
2417 errMsg += "' due to the following error: ";
2418 throw new MarshalException(errMsg + err);
2419 }
2420 return id;
2421 } //-- getID
2422
2423
2424 /**
2425 * Declares the given namespace, if not already in scope
2426 *
2427 * @param nsPrefix the namespace prefix
2428 * @param nsURI the namespace URI to declare
2429 * @return true if the namespace was not in scope and was
2430 * sucessfully declared, other false
2431 **/
2432 private boolean declareNamespace(String nsPrefix, String nsURI)
2433 {
2434 boolean declared = false;
2435
2436 //-- make sure it's not already declared...
2437 if ( (nsURI != null) && (nsURI.length() != 0)) {
2438
2439 String tmpURI = namespacesStack.getNamespaceURI(nsPrefix);
2440 if ((tmpURI != null) && (tmpURI.equals(nsURI))) {
2441 return declared;
2442 }
2443 String tmpPrefix = namespacesStack.getNamespacePrefix(nsURI);
2444 if ((tmpPrefix == null) || (!tmpPrefix.equals(nsPrefix))) {
2445 namespacesStack.addNamespace(nsPrefix, nsURI);
2446 declared = true;
2447 }
2448 }
2449 return declared;
2450 } //-- declareNamespace
2451
2452 /**
2453 * Sets the PrintWriter used for logging
2454 * @param printWriter the PrintWriter to use for logging
2455 **/
2456 public void setLogWriter(final PrintWriter printWriter) { }
2457
2458 /**
2459 * Sets the encoding for the serializer. Note that this method
2460 * cannot be called if you've passed in your own DocumentHandler.
2461 *
2462 * @param encoding the encoding to set
2463 **/
2464 public void setEncoding(String encoding) {
2465
2466 if (_serializer != null) {
2467 if (_format == null) {
2468 _format = getInternalContext().getOutputFormat();
2469 }
2470 _format.setEncoding(encoding);
2471 //-- reset output format, this needs to be done
2472 //-- any time a change occurs to the format.
2473 _serializer.setOutputFormat( _format );
2474 try {
2475 //-- Due to a Xerces Serializer bug that doesn't allow declaring
2476 //-- multiple prefixes to the same namespace, we use the old
2477 //-- DocumentHandler format and process namespaces ourselves
2478 _handler = new DocumentHandlerAdapter(_serializer.asDocumentHandler());
2479 }
2480 catch (java.io.IOException iox) {
2481 //-- we can ignore this exception since it shouldn't
2482 //-- happen. If _serializer is not null, it means
2483 //-- we've already called this method sucessfully
2484 //-- in the Marshaller() constructor
2485 if (LOG.isDebugEnabled()) {
2486 LOG.debug("Error setting encoding to " + encoding, iox);
2487 }
2488 }
2489 }
2490 else {
2491 String error = "encoding cannot be set if you've passed in "+
2492 "your own DocumentHandler";
2493 throw new IllegalStateException(error);
2494 }
2495 } //-- setEncoding
2496
2497 /**
2498 * Sets the value for the xsi:noNamespaceSchemaLocation attribute.
2499 * When set, this attribute will appear on the root element
2500 * of the marshalled document.
2501 *
2502 * @param schemaLocation the URI location of the schema
2503 * to which the marshalled document is an instance of.
2504 **/
2505 public void setNoNamespaceSchemaLocation(String schemaLocation) {
2506 if (schemaLocation == null) {
2507 //-- remove if necessary
2508 //-- to be added later.
2509 }
2510 else {
2511 _topLevelAtts.setAttribute(XSI_NO_NAMESPACE_SCHEMA_LOCATION,
2512 schemaLocation, XSI_NAMESPACE);
2513 }
2514 } //-- setNoNamespaceSchemaLocation
2515
2516 /**
2517 * Sets the value for the xsi:schemaLocation attribute.
2518 * When set, this attribute will appear on the root element
2519 * of the marshalled document.
2520 *
2521 * @param schemaLocation the URI location of the schema
2522 * to which the marshalled document is an instance of.
2523 **/
2524 public void setSchemaLocation(String schemaLocation) {
2525 if (schemaLocation == null) {
2526 //-- remove if necessary
2527 //-- to be added later.
2528 }
2529 else {
2530 _topLevelAtts.setAttribute(XSI_SCHEMA_LOCATION,
2531 schemaLocation, XSI_NAMESPACE);
2532 }
2533 } //-- setSchemaLocation
2534
2535 /**
2536 * Sets whether or not namespaces are output. By default
2537 * the Marshaller will output namespace declarations and
2538 * prefix elements and attributes with their respective
2539 * namespace prefix. This method can be used to prevent
2540 * the usage of namespaces.
2541 *
2542 * @param suppressNamespaces a boolean that when true
2543 * will prevent namespaces from being output.
2544 */
2545 public void setSuppressNamespaces(boolean suppressNamespaces) {
2546 _suppressNamespaces = suppressNamespaces;
2547 } //-- setSuppressNamespaces
2548
2549 /**
2550 * Sets whether or not the xsi:type attribute should appear
2551 * on the marshalled document.
2552 *
2553 * @param suppressXSIType a boolean that when true will prevent
2554 * xsi:type attribute from being used in the marshalling process.
2555 */
2556 public void setSuppressXSIType(boolean suppressXSIType) {
2557 _suppressXSIType = suppressXSIType;
2558 } //-- setSuppressXSIType
2559
2560 /**
2561 * Sets whether or not to output the xsi:type at the root
2562 * element. This is usually needed when the root element
2563 * type cannot be determined by the element name alone.
2564 * By default xsi:type will not be output on the root
2565 * element.
2566 *
2567 * @param useXSITypeAtRoot a boolean that when true indicates
2568 * that the xsi:type should be output on the root element.
2569 */
2570 public void setUseXSITypeAtRoot(boolean useXSITypeAtRoot) {
2571 _useXSITypeAtRoot = useXSITypeAtRoot;
2572 } //-- setUseXSITypeAtRoot
2573
2574 /**
2575 * Finds and returns an XMLClassDescriptor for the given class. If
2576 * a XMLClassDescriptor could not be found, this method will attempt to
2577 * create one automatically using reflection.
2578 * @param cls the Class to get the XMLClassDescriptor for
2579 * @exception MarshalException when there is a problem
2580 * retrieving or creating the XMLClassDescriptor for the given class
2581 **/
2582 private XMLClassDescriptor getClassDescriptor(final Class<?> cls)
2583 throws MarshalException {
2584 XMLClassDescriptor classDesc = null;
2585
2586 try {
2587 if (!isPrimitive(cls))
2588 classDesc = (XMLClassDescriptor) getResolver().resolve(cls);
2589 }
2590 catch(ResolverException rx) {
2591 Throwable actual = rx.getCause();
2592 if (actual instanceof MarshalException) {
2593 throw (MarshalException)actual;
2594 }
2595 if (actual != null) {
2596 throw new MarshalException(actual);
2597 }
2598 throw new MarshalException(rx);
2599 }
2600
2601 if (classDesc != null)
2602 classDesc = new InternalXMLClassDescriptor(classDesc);
2603
2604 return classDesc;
2605 } //-- getClassDescriptor
2606
2607 /**
2608 * Processes the attribute associated with the given attDescriptor and parent
2609 * object.
2610 *
2611 * @param atts the SAX attribute list to add the attribute to
2612 */
2613 private void processAttribute
2614 (Object object, XMLFieldDescriptor attDescriptor, AttributesImpl atts)
2615 throws MarshalException
2616 {
2617 if (attDescriptor == null) return;
2618
2619 //-- process Namespace nodes from Object Model,
2620 //-- if necessary.
2621 if (attDescriptor.getNodeType() == NodeType.Namespace) {
2622 if (!_suppressNamespaces) {
2623 Object map = attDescriptor.getHandler().getValue(object);
2624 MapHandler mapHandler = MapHandlers.getHandler(map);
2625 if (mapHandler != null) {
2626 Enumeration<?> keys = mapHandler.keys(map);
2627 while (keys.hasMoreElements()) {
2628 Object key = keys.nextElement();
2629 Object val = mapHandler.get(map, key);
2630 declareNamespace(key.toString(), val.toString());
2631 }
2632 }
2633 }
2634 return;
2635 }
2636
2637 String localName = attDescriptor.getXMLName();
2638 String qName = localName;
2639
2640 //-- handle attribute namespaces
2641 String namespace = "";
2642 if (!_suppressNamespaces) {
2643 namespace = attDescriptor.getNameSpaceURI();
2644 if (StringUtils.isNotEmpty(namespace)) {
2645 String prefix = attDescriptor.getNameSpacePrefix();
2646 if ((prefix == null) || (prefix.length() == 0))
2647 prefix = namespacesStack.getNonDefaultNamespacePrefix(namespace);
2648
2649 if ((prefix == null) || (prefix.length() == 0)) {
2650 //-- automatically create namespace prefix?
2651 prefix = DEFAULT_PREFIX + (++_namespaceCounter);
2652 }
2653 declareNamespace(prefix, namespace);
2654 qName = prefix + ':' + qName;
2655 }
2656 else namespace = "";
2657 }
2658
2659 Object value = null;
2660
2661 try {
2662 value = attDescriptor.getHandler().getValue(object);
2663 }
2664 catch(IllegalStateException ise) {
2665 LOG.warn("Error getting value from " + object, ise);
2666 return;
2667 }
2668
2669 //-- handle IDREF(S)
2670 if (attDescriptor.isReference() && (value != null)) {
2671
2672 if (attDescriptor.isMultivalued()) {
2673 Enumeration<?> enumeration = null;
2674 if (value instanceof Enumeration) {
2675 enumeration = (Enumeration<?>)value;
2676 }
2677 else {
2678 CollectionHandler<?> colHandler = null;
2679 try {
2680 colHandler = CollectionHandlers.getHandler(value.getClass());
2681 }
2682 catch(MappingException mx) {
2683 throw new MarshalException(mx);
2684 }
2685 enumeration = colHandler.elements(value);
2686 }
2687 if (enumeration.hasMoreElements()) {
2688 StringBuffer sb = new StringBuffer();
2689 for (int v = 0; enumeration.hasMoreElements(); v++) {
2690 if (v > 0) sb.append(' ');
2691 sb.append(getObjectID(enumeration.nextElement()).toString());
2692 }
2693 value = sb;
2694 }
2695 else value = null;
2696 }
2697 else {
2698 value = getObjectID(value);
2699 }
2700 }
2701 //-- handle multi-value attributes
2702 else if (attDescriptor.isMultivalued() && (value != null)) {
2703 value = processXSListType(value, attDescriptor);
2704 }
2705 else if (value != null) {
2706 //-- handle hex/base64 content
2707 Class<?> objType = value.getClass();
2708 if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) {
2709 value = encodeBinaryData(value, attDescriptor.getSchemaType());
2710 }
2711 }
2712
2713 if (value != null) {
2714 //check if the value is a QName that needs to
2715 //be resolved ({URI}value -> ns:value).
2716 String valueType = attDescriptor.getSchemaType();
2717 if ((valueType != null) && (valueType.equals(QNAME_NAME)))
2718 value = resolveQName(value, attDescriptor);
2719
2720 atts.addAttribute(namespace, localName, qName, CDATA, value.toString());
2721 }
2722 }
2723
2724 private Object processXSListType(final Object value, XMLFieldDescriptor descriptor)
2725 throws MarshalException {
2726 Object returnValue = null;
2727 Enumeration<?> enumeration = null;
2728 if (value instanceof Enumeration) {
2729 enumeration = (Enumeration<?>)value;
2730 }
2731 else {
2732 CollectionHandler<?> colHandler = null;
2733 try {
2734 colHandler = CollectionHandlers.getHandler(value.getClass());
2735 }
2736 catch(MappingException mx) {
2737 throw new MarshalException(mx);
2738 }
2739 enumeration = colHandler.elements(value);
2740 }
2741 if (enumeration.hasMoreElements()) {
2742 StringBuffer sb = new StringBuffer();
2743 for (int v = 0; enumeration.hasMoreElements(); v++) {
2744 if (v > 0) {
2745 sb.append(' ');
2746 }
2747 Object collectionValue = enumeration.nextElement();
2748 //-- handle hex/base64 content
2749 Class<?> objType = collectionValue.getClass();
2750 if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) {
2751 collectionValue = encodeBinaryData(collectionValue, descriptor.getComponentType());
2752 }
2753
2754 sb.append(collectionValue.toString());
2755 }
2756 returnValue = sb;
2757 }
2758
2759 return returnValue;
2760 }
2761
2762
2763 /**
2764 * Encode binary data.
2765 * @param valueToEncode The binary data to encode.
2766 * @param componentType The XML schema component type.
2767 * @return Encoded binary data in {@link String} form.
2768 */
2769 private Object encodeBinaryData(final Object valueToEncode,
2770 final String componentType) {
2771 String encodedValue;
2772 if (HexDecoder.DATA_TYPE.equals(componentType)) {
2773 encodedValue = HexDecoder.encode((byte[]) valueToEncode);
2774 } else {
2775 encodedValue = new String(Base64Encoder.encode((byte[]) valueToEncode));
2776 }
2777 return encodedValue;
2778 }
2779
2780
2781 /**
2782 * Processes the attributes for container objects
2783 *
2784 * @param target the object currently being marshalled.
2785 * @param classDesc the XMLClassDescriptor for the target object
2786 * @param atts the SAX attributes list to add attributes to
2787 */
2788 private void processContainerAttributes
2789 (Object target, XMLClassDescriptor classDesc, AttributesImpl atts)
2790 throws MarshalException
2791 {
2792 if (classDesc instanceof XMLClassDescriptorImpl) {
2793 if (!((XMLClassDescriptorImpl)classDesc).hasContainerFields())
2794 return;
2795 }
2796
2797 XMLFieldDescriptor[] elemDescriptors = classDesc.getElementDescriptors();
2798 for (int i = 0; i < elemDescriptors.length; i++) {
2799 if (elemDescriptors[i] == null) continue;
2800 if (!elemDescriptors[i].isContainer()) continue;
2801 processContainerAttributes(target, elemDescriptors[i], atts);
2802 }
2803 } //-- processContainerAttributes
2804
2805 /**
2806 * Processes the attributes for container objects.
2807 *
2808 * @param target the object currently being marshalled.
2809 * @param containerFieldDesc the XMLFieldDescriptor for the containter to process
2810 * @param atts the SAX attributes list to add any necessary attributes to.
2811 * @throws MarshalException If there's a problem marshalling the attribute(s).
2812 */
2813 private void processContainerAttributes
2814 (final Object target, final XMLFieldDescriptor containerFieldDesc,
2815 final AttributesImpl atts)
2816 throws MarshalException {
2817 if (target.getClass().isArray()) {
2818 int length = Array.getLength(target);
2819 for (int j = 0; j < length; j++) {
2820 Object item = Array.get(target, j);
2821 if (item != null) {
2822 processContainerAttributes(item, containerFieldDesc, atts);
2823 }
2824 }
2825 return;
2826 } else if (target instanceof Enumeration) {
2827 Enumeration<?> enumeration = (Enumeration<?>) target;
2828 while (enumeration.hasMoreElements()) {
2829 Object item = enumeration.nextElement();
2830 if (item != null) {
2831 processContainerAttributes(item, containerFieldDesc, atts);
2832 }
2833 }
2834 return;
2835 }
2836
2837 Object containerObject = containerFieldDesc.getHandler().getValue(target);
2838
2839 if (containerObject == null) {
2840 return;
2841 }
2842
2843 XMLClassDescriptor containerClassDesc
2844 = (XMLClassDescriptor) containerFieldDesc.getClassDescriptor();
2845
2846 if (containerClassDesc == null) {
2847 containerClassDesc = getClassDescriptor(containerFieldDesc.getFieldType());
2848 if (containerClassDesc == null) {
2849 return;
2850 }
2851 }
2852
2853 // Look for attributes
2854 XMLFieldDescriptor[] attrDescriptors = containerClassDesc.getAttributeDescriptors();
2855 for (int idx = 0; idx < attrDescriptors.length; idx++) {
2856 if (attrDescriptors[idx] == null) {
2857 continue;
2858 }
2859 if (attrDescriptors[idx].getLocationPath() == null
2860 || attrDescriptors[idx].getLocationPath().length() == 0) {
2861 processAttribute(containerObject, attrDescriptors[idx], atts);
2862 }
2863 }
2864
2865 // recursively process containers
2866 processContainerAttributes(containerObject, containerClassDesc, atts);
2867 } //-- processContainerAttributes
2868
2869 /**
2870 * Resolve a QName value ({URI}value) by declaring a namespace after
2871 * having retrieved the prefix.
2872 */
2873 private Object resolveQName(Object value, XMLFieldDescriptor fieldDesc) {
2874 if ( (value == null) || !(value instanceof String))
2875 return value;
2876 if (!(fieldDesc instanceof XMLFieldDescriptorImpl))
2877 return value;
2878
2879 String result = (String)value;
2880
2881 String nsURI = null;
2882 int idx = -1;
2883 if ((result.length() > 0) && (result.charAt(0) == '{')) {
2884 idx = result.indexOf('}');
2885 if (idx <= 0) {
2886 String err = "Bad QName value :'"+result+"', it should follow the pattern '{URI}value'";
2887 throw new IllegalArgumentException(err);
2888 }
2889 nsURI = result.substring(1, idx);
2890 }
2891 else return value;
2892
2893 String prefix = ((XMLFieldDescriptorImpl)fieldDesc).getQNamePrefix();
2894 //no prefix provided, check if one has been previously defined
2895 if (prefix == null)
2896 prefix = namespacesStack.getNamespacePrefix(nsURI);
2897 //if still no prefix, use a naming algorithm (ns+counter).
2898 if (prefix == null)
2899 prefix = DEFAULT_PREFIX+(++_namespaceCounter);
2900 result = (prefix.length() != 0)?prefix+":"+result.substring(idx+1):result.substring(idx+1);
2901 declareNamespace(prefix, nsURI);
2902 return result;
2903 }
2904
2905 private void validate(final Object object) throws ValidationException {
2906 if (_validate) {
2907 //-- we must have a valid element before marshalling
2908 Validator validator = new Validator();
2909 ValidationContext context = new ValidationContext();
2910 context.setInternalContext(getInternalContext());
2911 // context.setConfiguration(_config);
2912 // context.setResolver(_cdResolver);
2913 validator.validate(object, context);
2914 }
2915 }
2916
2917 /**
2918 * Returns the value of the given Castor XML-specific property.
2919 * @param name Qualified name of the CASTOR XML-specific property.
2920 * @return The current value of the given property.
2921 * @since 1.1.2
2922 */
2923 public String getProperty(final String name) {
2924 return getInternalContext().getStringProperty(name);
2925 }
2926
2927 /**
2928 * Sets a custom value of a given Castor XML-specific property.
2929 * @param name Name of the Castor XML property
2930 * @param value Custom value to set.
2931 * @since 1.1.2
2932 */
2933 public void setProperty(final String name, final String value) {
2934 getInternalContext().setProperty(name, value);
2935 deriveProperties();
2936 }
2937
2938 /**
2939 * Inner-class used for handling wrapper elements
2940 * and locations.
2941 */
2942 static class WrapperInfo {
2943 private String _localName = null;
2944 private String _qName = null;
2945 private String _location = null;
2946
2947 WrapperInfo(final String localName, final String qName, final String location) {
2948 _localName = localName;
2949 _qName = qName;
2950 _location = location;
2951 }
2952 }
2953
2954
2955 static class MarshalState {
2956 private String _xpath = null;
2957 private XMLFieldDescriptor[] _nestedAtts = null;
2958 private int _nestedAttCount = 0;
2959 private MarshalState _parent = null;
2960 private Object _owner = null;
2961 private String _xmlName = null;
2962
2963 MarshalState(Object owner, String xmlName) {
2964 checkNotNull(owner, "The argument 'owner' must not be null.");
2965 checkNotNull(xmlName, "The argument 'xmlName' must not be null.");
2966
2967 _owner = owner;
2968 _xmlName = xmlName;
2969 }
2970
2971
2972 MarshalState createMarshalState(Object owner, String xmlName) {
2973 MarshalState ms = new MarshalState(owner, xmlName);
2974 ms._parent = this;
2975 return ms;
2976 }
2977
2978 String getXPath() {
2979 if (_xpath == null) {
2980 if (_parent != null) {
2981 _xpath = _parent.getXPath() + "/" + _xmlName;
2982 }
2983 else {
2984 _xpath = _xmlName;
2985 }
2986 }
2987 return _xpath;
2988 }
2989
2990 Object getOwner() {
2991 return _owner;
2992 }
2993
2994 MarshalState getParent() {
2995 return _parent;
2996 }
2997 }
2998
2999 /**
3000 * A wrapper for a "Nil" object
3001 *
3002 */
3003 public static class NilObject {
3004
3005 private XMLClassDescriptor _classDesc = null;
3006 private XMLFieldDescriptor _fieldDesc = null;
3007
3008 NilObject(XMLClassDescriptor classDesc, XMLFieldDescriptor fieldDesc) {
3009 _classDesc = classDesc;
3010 _fieldDesc = fieldDesc;
3011 }
3012
3013 /**
3014 * Returns the associated XMLClassDescriptor
3015 *
3016 * @return the XMLClassDescriptor
3017 */
3018 public XMLClassDescriptor getClassDescriptor() {
3019 return _classDesc;
3020 }
3021
3022 /**
3023 * Returns the associated XMLFieldDescriptor
3024 *
3025 * @return the associated XMLFieldDescriptor
3026 */
3027 public XMLFieldDescriptor getFieldDescriptor() {
3028 return _fieldDesc;
3029 }
3030 }
3031
3032 /**
3033 * To set the SAX {@link ContentHandler} which is used as destination at marshalling.
3034 * @param contentHandler the SAX {@link ContentHandler} to use as destination at marshalling
3035 */
3036 public void setContentHandler(final ContentHandler contentHandler) {
3037 _handler = contentHandler;
3038 }
3039
3040 /**
3041 * Assigns the document handler, ignoring any possible exception.
3042 */
3043 private void setDocumentHandler() {
3044 try {
3045 //-- Due to a Xerces Serializer bug that doesn't allow declaring
3046 //-- multiple prefixes to the same namespace, we use the old
3047 //-- DocumentHandler format and process namespaces ourselves
3048 _handler = new DocumentHandlerAdapter(_serializer.asDocumentHandler());
3049 }
3050 catch (IOException iox) {
3051 //-- we can ignore this exception since it shouldn't
3052 //-- happen. If _serializer is not null, it means
3053 //-- we've already called this method successfully
3054 //-- in the Marshaller() constructor
3055 if (LOG.isDebugEnabled()) {
3056 LOG.debug("Error setting up document handler", iox);
3057 }
3058 }
3059 }
3060
3061 /**
3062 * Checks if passed parameter is not null and not a empty string. In case it is, a
3063 * {@link IllegalArgumentException} is thrown.
3064 *
3065 * @param param the parameter to check
3066 * @param msg the error message to use for thrown exception
3067 *
3068 * @throws IllegalArgumentException if param is null
3069 */
3070 private static void checkNotEmpty(String param, String msg) {
3071 checkNotNull(param, msg);
3072
3073 if (param.length() == 0) {
3074 throw new IllegalArgumentException(msg);
3075 }
3076 }
3077
3078 /**
3079 * Checks if passed parameter is not null. In case it is, a {@link IllegalArgumentException} is thrown.
3080 *
3081 * @param param the parameter to check
3082 * @param msg the error message to use for thrown exception
3083 *
3084 * @throws IllegalArgumentException if param is null
3085 */
3086 private static void checkNotNull(Object param, String msg) {
3087
3088 if (param == null) {
3089 throw new IllegalArgumentException(msg);
3090 }
3091 }
3092 } //-- Marshaller
3093
3094