/**
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided
* that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright
* statements and notices. Redistributions must also contain a
* copy of this document.
*
* 2. Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. The name "Exolab" must not be used to endorse or promote
* products derived from this Software without prior written
* permission of Intalio, Inc. For written permission,
* please contact info@exolab.org.
*
* 4. Products derived from this Software may not be called "Exolab"
* nor may "Exolab" appear in their names without prior written
* permission of Intalio, Inc. Exolab is a registered
* trademark of Intalio, Inc.
*
* 5. Due credit should be given to the Exolab Project
* (http://www.exolab.org/).
*
* THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 1999-2004 (C) Intalio, Inc. All Rights Reserved.
*
* This file was originally developed by Keith Visco during the
* course of employment at Intalio Inc.
* All portions of this file developed by Keith Visco after Jan 19 2005 are
* Copyright (C) 2005 Keith Visco. All Rights Reserved.
*
* $Id: Marshaller.java 8057 2009-02-05 22:26:22Z jgrueneis $
*/
package org.exolab.castor.xml;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.core.util.Base64Encoder;
import org.castor.core.util.HexDecoder;
import org.castor.core.util.Messages;
import org.castor.mapping.BindingType;
import org.castor.mapping.MappingUnmarshaller;
import org.castor.xml.BackwardCompatibilityContext;
import org.castor.xml.InternalContext;
import org.castor.xml.XMLProperties;
import org.exolab.castor.mapping.CollectionHandler;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.mapping.MapHandler;
import org.exolab.castor.mapping.MapItem;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.mapping.MappingLoader;
import org.exolab.castor.mapping.handlers.MapHandlers;
import org.exolab.castor.mapping.loader.CollectionHandlers;
import org.exolab.castor.types.AnyNode;
import org.exolab.castor.util.SafeStack;
import org.exolab.castor.xml.descriptors.RootArrayDescriptor;
import org.exolab.castor.xml.descriptors.StringClassDescriptor;
import org.exolab.castor.xml.handlers.DateFieldHandler;
import org.exolab.castor.xml.handlers.EnumFieldHandler;
import org.exolab.castor.xml.util.AnyNode2SAX2;
import org.exolab.castor.xml.util.AttributeSetImpl;
import org.exolab.castor.xml.util.DocumentHandlerAdapter;
import org.exolab.castor.xml.util.SAX2DOMHandler;
import org.exolab.castor.xml.util.XMLClassDescriptorAdapter;
import org.exolab.castor.xml.util.XMLClassDescriptorImpl;
import org.exolab.castor.xml.util.XMLFieldDescriptorImpl;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.DocumentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
/**
* A Marshaller that serializes Java Object's to XML
*
* Note: This class is not thread safe, and not intended to be,
* so please create a new Marshaller for each thread if it
* is to be used in a multithreaded environment.
*
* @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
* @version $Revision: 8057 $ $Date: 2006-04-13 06:47:36 -0600 (Thu, 13 Apr 2006) $
*/
public class Marshaller extends MarshalFramework {
//---------------------------/
//- Private Class variables -/
//---------------------------/
/**
* Logger from commons-logging.
*/
private static final Log LOG = LogFactory.getLog(Marshaller.class);
/**
* The CDATA type..uses for SAX attributes.
**/
private static final String CDATA = "CDATA";
/**
* Default prefix for use when creating
* namespace prefixes.
**/
private static final String DEFAULT_PREFIX = "ns";
/**
* Message name for a non sax capable serializer error.
**/
private static final String SERIALIZER_NOT_SAX_CAPABLE
= "conf.serializerNotSaxCapable";
/**
* Namespace declaration for xml schema instance.
**/
private static final String XSI_PREFIX = "xsi";
/**
* The xsi:type attribute.
**/
private static final String XSI_TYPE = "xsi:type";
/**
* Namespace prefix counter.
**/
private int NAMESPACE_COUNTER = 0;
/**
* An instance of StringClassDescriptor.
**/
private static final StringClassDescriptor _StringClassDescriptor
= new StringClassDescriptor();
//----------------------------/
//- Private member variables -/
//----------------------------/
/**
* A boolean to indicate whether or not we are
* marshalling as a complete document or not.
**/
private boolean _asDocument = true;
/**
* The depth of the sub tree, 0 denotes document level.
**/
int depth = 0;
/**
* The output format to use with the serializer.
* This will be null if the user passed in their
* own DocumentHandler.
**/
private OutputFormat _format = null;
/**
* The ContentHandler we are marshalling to.
**/
private ContentHandler _handler = null;
/**
* flag to indicate whether or not to use xsi:type.
**/
private boolean _marshalExtendedType = true;
/**
* The registered MarshalListener to receive
* notifications of pre and post marshal for
* each object in the tree being marshalled.
**/
private MarshalListener _marshalListener = null;
/**
* The namespace stack.
**/
private Namespaces _namespaces = new Namespaces();
/**
* Records Java packages being used during marshalling.
**/
private List _packages = new ArrayList();
/**
* A stack of parent objects...to prevent circular
* references from being marshalled.
**/
private Stack _parents = new SafeStack();
/**
* A list of ProcessingInstructions to output
* upon marshalling of the document.
**/
private List _processingInstructions = new ArrayList();
/**
* Name of the root element to use.
*/
private String _rootElement = null;
/**
* A boolean to indicate keys from a map
* should be saved when necessary.
*/
private boolean _saveMapKeys = true;
/**
* The serializer that is being used for marshalling.
* This may be null if the user passed in a DocumentHandler.
**/
private Serializer _serializer = null;
/**
* A flag to allow suppressing namespaces.
*/
private boolean _suppressNamespaces = false;
/**
* A flag to allow suppressing the xsi:type attribute.
*/
private boolean _suppressXSIType = false;
private boolean _useXSITypeAtRoot = false;
/**
* The set of optional top-level attributes
* set by the user.
**/
private AttributeSetImpl _topLevelAtts = new AttributeSetImpl();
/**
* The AttributeList which is to be used during marshalling,
* instead of creating a bunch of new ones.
*/
private AttributesImpl _attributes = new AttributesImpl();
/**
* The validation flag.
*/
private boolean _validate = false;
/** Set of full class names of proxy interfaces. If the class to be marshalled implements
* one of them the superclass will be marshalled instead of the class itself. */
private final Set _proxyInterfaces = new HashSet();
/**
* Creates a new {@link Marshaller} with the given SAX {@link DocumentHandler}.
*
* @param handler the SAX {@link DocumentHandler} to "marshal" to.
*
* @deprecate Please use {@link XMLContext#createMarshaller()} and
* {@link Marshaller#setDocumentHandler(DocumentHandler)} instead
*
* @see {@link XMLContext#createMarshaller()}
* @see {@link Marshaller#setDocumentHandler(DocumentHandler)}
* @see XMLContext
*
**/
public Marshaller(final DocumentHandler handler) {
super(null);
if (handler == null) {
throw new IllegalArgumentException("The given 'org.sax.DocumentHandler' "
+ "instance is null.");
}
setContentHandler(new DocumentHandlerAdapter(handler));
}
/**
* Sets the given SAX {@link DocumentHandler} to 'marshal' into.
*
* @param handler the SAX {@link DocumentHandler} to "marshal" to.
**/
public void setDocumentHandler(final DocumentHandler handler) {
if (handler == null) {
throw new IllegalArgumentException("The given 'org.sax.DocumentHandler' "
+ "instance is null.");
}
setContentHandler(new DocumentHandlerAdapter(handler));
}
/**
* Creates a new {@link Marshaller} with the given SAX {@link ContentHandler}.
*
* @param contentHandler the {@link ContentHandler} to "marshal" to.
*
* @deprecate Please use {@link XMLContext#createMarshaller()} and
* {@link Marshaller#setContentHandler(ContentHandler)} instead
*
* @see {@link XMLContext#createMarshaller()}
* @see {@link Marshaller#setContentHandler(ContentHandler)}
* @see XMLContext
*
**/
public Marshaller(final ContentHandler contentHandler) {
super(null);
if (contentHandler == null) {
throw new IllegalArgumentException("The given 'org.sax.ContentHandler' is null.");
}
setContentHandler(contentHandler);
}
/**
* The one {@link Marshaller} constructor that is used by {@link XMLContext} which
* sets an {@link InternalContext} that comes from outside. Writer or {@link ContentHandler}
* have to be set in a second step.
* @param internalContext the {@link InternalContext} to initialize the {@link Marshaller}
* instance with
*/
public Marshaller(final InternalContext internalContext) {
super(internalContext);
}
/**
* Creates a default instance of Marshaller, where the sink needs to be set
* separately.
*/
public Marshaller () {
super(null);
}
/**
* Creates a new Marshaller with the given writer.
* @param out the Writer to serialize to
* @throws IOException If the given {@link Writer} instance cannot be opened.
* @deprecate Please use {@link XMLContext#createMarshaller()} and
* {@link Marshaller#setWriter(Writer)} instead
*
* @see {@link XMLContext#createMarshaller()}
* @see {@link Marshaller#setWriter(Writer)}
* @see XMLContext
*
**/
public Marshaller(final Writer out) throws IOException {
super(null);
setWriter(out);
}
/**
* Sets the java.io.Writer to be used during marshalling.
*
* @param out
* The writer to use for marshalling
* @throws IOException
* If there's a problem accessing the java.io.Writer provided
*/
public void setWriter (final Writer out) throws IOException {
if (out == null) {
throw new IllegalArgumentException("The given 'java.io.Writer instance' is null.");
}
configureSerializer(out);
}
private void configureSerializer(Writer out) throws IOException
{
_serializer = getInternalContext().getSerializer();
if (_serializer == null)
throw new RuntimeException("Unable to obtain serializer");
_serializer.setOutputCharStream( out );
//-- Due to a Xerces Serializer bug that doesn't allow declaring
//-- multiple prefixes to the same namespace, we use the old
//-- DocumentHandler format and process namespaces ourselves
_handler = new DocumentHandlerAdapter(_serializer.asDocumentHandler());
if ( _handler == null ) {
String err = Messages.format( SERIALIZER_NOT_SAX_CAPABLE,
_serializer.getClass().getName() );
throw new RuntimeException( err );
}
}
/**
* Creates a new {@link Marshaller} for the given DOM {@link Node}.
*
* @param node the DOM {@link Node} to marshal into.
*
* @deprecate Please use {@link XMLContext#createMarshaller()} and
* {@link Marshaller#setNode(Node)} instead
*
* @see {@link XMLContext#createMarshaller()}
* @see {@link Marshaller#setNode(Node)}
* @see XMLContext
*
*
**/
public Marshaller(final Node node) {
super(null);
if (node == null) {
throw new IllegalArgumentException("The given org.w3c.dom.Node instance is null.");
}
setContentHandler(new DocumentHandlerAdapter(new SAX2DOMHandler(node)));
}
/**
* Sets the W3C {@link Node} instance to marshal to.
*
* @param node the DOM {@link Node} to marshal into.
**/
public void setNode(final Node node) {
if (node == null) {
throw new IllegalArgumentException("The given org.w3c.dom.Node instance is null.");
}
setContentHandler(new DocumentHandlerAdapter(new SAX2DOMHandler(node)));
}
/**
* To set the {@link InternalContext} to use, and to
* initialize {@link Marshaller} properties linked to it.
* @param internalContext the {@link InternalContext} to use
*/
public void setInternalContext(final InternalContext internalContext) {
super.setInternalContext(internalContext);
deriveProperties();
}
/**
* Derive class-level properties from {@link XMLProperties} as defined
* {@link InternalContext}. This method will be called after a new
* {@link InternalContext} or a property has been set.
*
* @link #setInternalContext(InternalContext)
*/
private void deriveProperties() {
_validate = getInternalContext().marshallingValidation();
_saveMapKeys = getInternalContext().getBooleanProperty(
XMLProperties.SAVE_MAP_KEYS).booleanValue();
String prop = getInternalContext().getStringProperty(
XMLProperties.PROXY_INTERFACES);
if (prop != null) {
StringTokenizer tokenizer = new StringTokenizer(prop, ", ");
while (tokenizer.hasMoreTokens()) {
_proxyInterfaces.add(tokenizer.nextToken());
}
}
}
/**
* Adds the given processing instruction data to the set of
* processing instructions to output during marshalling.
*
* @param target the processing instruction target
* @param data the processing instruction data
**/
public void addProcessingInstruction(String target, String data) {
if ((target == null) || (target.length() == 0)) {
String err = "the argument 'target' must not be null or empty.";
throw new IllegalArgumentException(err);
}
if (data == null) {
String err = "the argument 'data' must not be null.";
throw new IllegalArgumentException(err);
}
_processingInstructions.add(new ProcessingInstruction(target, data));
} //-- addProcessingInstruction
/**
* Sets the document type definition for the serializer. Note that this method
* cannot be called if you've passed in your own DocumentHandler.
*
* @param publicId the public identifier
* @param systemId the system identifier
*/
public void setDoctype(String publicId, String systemId) {
if (_serializer != null) {
if (_format == null) {
_format = getInternalContext().getOutputFormat();
}
_format.setDoctype(publicId, systemId);
//-- reset output format, this needs to be done
//-- any time a change occurs to the format.
_serializer.setOutputFormat( _format );
try {
//-- Due to a Xerces Serializer bug that doesn't allow declaring
//-- multiple prefixes to the same namespace, we use the old
//-- DocumentHandler format and process namespaces ourselves
_handler = new DocumentHandlerAdapter(_serializer.asDocumentHandler());
}
catch (java.io.IOException iox) {
//-- we can ignore this exception since it shouldn't
//-- happen. If _serializer is not null, it means
//-- we've already called this method sucessfully
//-- in the Marshaller() constructor
if (LOG.isDebugEnabled()) {
LOG.debug("Error setting up document handler", iox);
}
}
}
else {
String error = "doctype cannot be set if you've passed in "+
"your own DocumentHandler";
throw new IllegalStateException(error);
}
} //-- setDoctype
/**
* Sets whether or not to marshal as a document which includes
* the XML declaration, and if necessary the DOCTYPE declaration.
* By default the Marshaller will marshal as a well formed
* XML document including the XML Declaration.
*
* If the given boolean is true, the Marshaller will marshal
* as a well formed XML fragment (no XML declaration or DOCTYPE).
*
* This method is basically the same as calling
* #setMarshalAsDocument(false);
*
* @param supressXMLDeclaration a boolean that when true
* includes that generated XML should not contain
* the XML declaration.
* @see #setMarshalAsDocument
*/
public void setSupressXMLDeclaration(boolean supressXMLDeclaration) {
setMarshalAsDocument(!supressXMLDeclaration);
} //-- setSupressXMLDeclaration
/**
* Sets whether or not to marshal as a document which includes
* the XML declaration, and if necessary the DOCTYPE declaration.
* By default the Marshaller will marshal as a well formed
* XML document including the XML Declaration.
*
* If the given boolean is false, the Marshaller will marshal
* as a well formed XML fragment (no XML declaration or DOCTYPE).
*
* This method is basically the same as calling
* #setSupressXMLDeclaration(true);
*
* @param asDocument a boolean, when true, indicating to marshal
* as a complete XML document.
* @see #setSupressXMLDeclaration
*/
public void setMarshalAsDocument(boolean asDocument) {
_asDocument = asDocument;
if (_serializer != null) {
if (_format == null) {
_format = getInternalContext().getOutputFormat();
}
_format.setOmitXMLDeclaration( ! asDocument );
_format.setOmitDocumentType( ! asDocument );
//-- reset output format, this needs to be done
//-- any time a change occurs to the format.
_serializer.setOutputFormat( _format );
try {
//-- Due to a Xerces Serializer bug that doesn't allow declaring
//-- multiple prefixes to the same namespace, we use the old
//-- DocumentHandler format and process namespaces ourselves
_handler = new DocumentHandlerAdapter(_serializer.asDocumentHandler());
}
catch (java.io.IOException iox) {
//-- we can ignore this exception since it shouldn't
//-- happen. If _serializer is not null, it means
//-- we've already called this method sucessfully
//-- in the Marshaller() constructor
if (LOG.isDebugEnabled()) {
LOG.debug("Error setting up document handler", iox);
}
}
}
} //-- setMarshalAsDocument
/**
* Sets the given mapping to be used by the marshalling Framework. If a resolver
* exists this mapping will be added to the existing ClassDescriptorResolver.
* Otherwise a new ClassDescriptorResolver will be created.
*
* @param mapping Mapping to using during marshalling.
*/
public void setMapping(final Mapping mapping) throws MappingException {
// if (_cdResolver == null) {
// _cdResolver = (XMLClassDescriptorResolver) ClassDescriptorResolverFactory
// .createClassDescriptorResolver(BindingType.XML);
// }
if ((getInternalContext() == null)
|| (getInternalContext().getXMLClassDescriptorResolver() == null)) {
String message = "No internal context or no class descriptor in context.";
LOG.warn(message);
throw new IllegalStateException(message);
}
MappingUnmarshaller mum = new MappingUnmarshaller();
MappingLoader resolver = mum.getMappingLoader(mapping, BindingType.XML);
getInternalContext().getXMLClassDescriptorResolver().setMappingLoader(resolver);
}
/**
* Sets an optional MarshalListener to recieve pre and post
* marshal notification for each Object in the tree.
* MarshalListener is only for complex objects that map
* into elements, simpleTypes and types that map into
* attributes do not cause any pre and post event notifications.
* Current only one (1) listener is allowed. If you need
* register multiple listeners, you will have to create
* your own master listener that will forward the
* event notifications and manage the multiple
* listeners.
*
* @param listener the MarshalListener to set.
**/
public void setMarshalListener(MarshalListener listener) {
_marshalListener = listener;
} //-- setMarshalListener
/**
* Sets the mapping for the given Namespace prefix
* @param nsPrefix the namespace prefix
* @param nsURI the namespace that the prefix resolves to
**/
public void setNamespaceMapping(String nsPrefix, String nsURI) {
if ((nsURI == null) || (nsURI.length() == 0)) {
String err = "namespace URI must not be null.";
throw new IllegalArgumentException(err);
}
_namespaces.addNamespace(nsPrefix, nsURI);
} //-- setNamespacePrefix
/**
* Sets the name of the root element to use.
*
* @param rootElement The name of the root element to use.
*/
public void setRootElement(String rootElement) {
_rootElement = rootElement;
} //-- setRootElement
/**
* Returns the name of the root element to use
* @return Returns the name of the root element to use
*/
public String getRootElement()
{
return _rootElement;
} //-- getRootElement
/**
* Set to True to declare the given namespace mappings at the root node. Default is False.
* @param nsPrefixAtRoot
* @deprecated
*/
public void setNSPrefixAtRoot(boolean nsPrefixAtRoot)
{
// leaving for now...backward compatability
//_nsPrefixAtRoot = nsPrefixAtRoot;
}
/**
* Returns True if the given namespace mappings will be declared at the root node.
* @return Returns True if the given namespace mappings will be declared at the root node.
* @deprecated
*/
public boolean getNSPrefixAtRoot()
{
return true;
}
/**
* Returns the ClassDescriptorResolver for use during marshalling
*
* @return the ClassDescriptorResolver
* @see #setResolver
*/
public XMLClassDescriptorResolver getResolver() {
// if (_cdResolver == null) {
// _cdResolver = (XMLClassDescriptorResolver)
// ClassDescriptorResolverFactory.createClassDescriptorResolver(BindingType.XML);
// }
if ((getInternalContext() == null)
|| (getInternalContext().getXMLClassDescriptorResolver() == null)) {
String message = "No internal context or no class descriptor in context.";
LOG.warn(message);
throw new IllegalStateException(message);
}
return getInternalContext().getXMLClassDescriptorResolver();
} //-- getResolver
/**
* Sets the ClassDescriptorResolver to use during marshalling.
*
* <BR />
* <B>Note:</B> This method will nullify any Mapping
* currently being used by this Marshaller
*
* @param cdr the ClassDescriptorResolver to use
* @see #setMapping
* @see #getResolver
*/
public void setResolver(final XMLClassDescriptorResolver cdr) {
if (cdr != null) {
getInternalContext().setXMLClassDescriptorResolver(cdr);
// _cdResolver = cdr;
}
} //-- setResolver
/**
* Sets whether or not to validate the object model
* before marshalling. By default validation is enabled.
* This method is really for debugging.
* I do not recommend turning off validation, since
* you could marshal a document, which you can then
* not unmarshal. If you know the object model
* is guaranteed to be valid, disabling validation will
* improve performace.
*
* @param validate the boolean indicating whether or not to
* validate the object model before marshalling.
**/
public void setValidation(boolean validate) {
_validate = validate;
} //-- setValidation
public boolean getValidation() {
return _validate;
}
/**
* If True the marshaller will use the 'xsi:type' attribute
* to marshall a field value that extended the defined field type.
* Default is True.
*/
public void setMarshalExtendedType(boolean marshalExtendedType)
{
_marshalExtendedType = marshalExtendedType;
} //-- setMarshalExtendedType
/**
* If True the marshaller will use the 'xsi:type' attribute
* to marshall a field value that extended the defined field type.
* Default is True.
* @return If True the marshaller will use the 'xsi:type' attribute
* to marshall a field value that extended the defined field type.
* Default is True.
*/
public boolean getMarshalExtendedType()
{
return _marshalExtendedType;
} //-- setMarshallExtendedType
/**
* Marshals the given Object as XML using the given writer.
*
* @param object The Object to marshal.
* @param out The writer to marshal to.
* @exception org.exolab.castor.xml.MarshalException
* @exception org.exolab.castor.xml.ValidationException
*/
public static void marshal(Object object, Writer out)
throws MarshalException, ValidationException {
try {
staticMarshal(object, new Marshaller(out));
} catch (IOException e) {
throw new MarshalException(e);
}
} //-- marshal
/**
* Marshals the given Object as XML using the given DocumentHandler
* to send events to.
*
* @param object The Object to marshal.
* @param handler The DocumentHandler to marshal to.
* @exception org.exolab.castor.xml.MarshalException
* @exception org.exolab.castor.xml.ValidationException
*/
public static void marshal(Object object, DocumentHandler handler)
throws MarshalException, ValidationException {
staticMarshal(object, new Marshaller(handler));
} //-- marshal
/**
* Marshals the given Object as XML using the given ContentHandler
* to send events to.
*
* @param object The Object to marshal.
* @param handler The ContentHandler to marshal to.
* @exception org.exolab.castor.xml.MarshalException
* @exception org.exolab.castor.xml.ValidationException
*/
public static void marshal(Object object, ContentHandler handler)
throws MarshalException, ValidationException, IOException {
staticMarshal(object, new Marshaller(handler));
} //-- marshal
/**
* Marshals the given Object as XML using the given DOM Node
* to send events to.
*
* @param object The Object to marshal.
* @param node The DOM Node to marshal to.
* @exception org.exolab.castor.xml.MarshalException
* @exception org.exolab.castor.xml.ValidationException
*/
public static void marshal(Object object, Node node)
throws MarshalException, ValidationException {
staticMarshal(object, new Marshaller(node));
} //-- marshal
/**
* Static helper method to marshal the given object using the
* Marshaller instance provided.
*
* @param object The Object to marshal.
* @param marshaller The {@link Marshaller} to use for marshalling.
* @throws MarshalException as thrown by marshal(Object)
* @throws ValidationException as thrown by marshal(Object)
*/
private static void staticMarshal(final Object object, final Marshaller marshaller)
throws MarshalException, ValidationException {
if (object == null) {
throw new MarshalException("object must not be null");
}
if (LOG.isInfoEnabled()) {
LOG.info("Marshaller called using one of the *static* " +
" marshal(Object, *) methods. This will ignore any " +
" mapping files as specified. Please consider switching to " +
" using Marshaller instances and calling one of the" +
" marshal(*) methods.");
}
marshaller.marshal(object);
} //-- staticMarshal
/**
* Marshals the given Object as XML using the DocumentHandler
* for this Marshaller.
*
* @param object The Object to marshal.
* @exception org.exolab.castor.xml.MarshalException
* @exception org.exolab.castor.xml.ValidationException
*/
public void marshal(Object object)
throws MarshalException, ValidationException {
if (object == null)
throw new MarshalException("object must not be null");
if (LOG.isDebugEnabled()) {
LOG.debug("Marshalling " + object.getClass().getName());
}
if (object instanceof AnyNode) {
try{
AnyNode2SAX2.fireEvents((AnyNode)object, _handler, _namespaces);
} catch(SAXException e) {
throw new MarshalException(e);
}
}
else {
validate(object);
MarshalState mstate = new MarshalState(object, "root");
if (_asDocument) {
try {
_handler.startDocument();
//-- handle processing instructions
for (int i = 0; i < _processingInstructions.size(); i++) {
ProcessingInstruction pi = (ProcessingInstruction)
_processingInstructions.get(i);
_handler.processingInstruction(pi.getTarget(),
pi.getData());
}
marshal(object, null, _handler, mstate);
_handler.endDocument();
} catch (SAXException sx) {
throw new MarshalException(sx);
}
}
else {
marshal(object, null, _handler, mstate);
}
}
} //-- marshal
/**
* Marshals the given object, using the given descriptor
* and document handler.
*
* <BR/>
* <B>Note:</B>
* <I>
* It is an error if this method is called with an
* AttributeDescriptor.
* </I>
* @param descriptor the XMLFieldDescriptor for the given object
* @param handler the DocumentHandler to marshal to
* @exception org.exolab.castor.xml.MarshalException
* @exception org.exolab.castor.xml.ValidationException
* during marshaling
**/
private void marshal
(Object object,
XMLFieldDescriptor descriptor,
ContentHandler handler,
final MarshalState mstate)
throws MarshalException, ValidationException
{
if (object == null) {
String err = "Marshaller#marshal: null parameter: 'object'";
throw new MarshalException(err);
}
if (descriptor != null && descriptor.isTransient())
return;
//-- notify listener
if (_marshalListener != null) {
if (!_marshalListener.preMarshal(object))
return;
}
//-- handle AnyNode
if (object instanceof AnyNode) {
try {
AnyNode2SAX2.fireEvents((AnyNode) object, handler, _namespaces);
}catch (SAXException e) {
throw new MarshalException(e);
}
return;
}
boolean containerField = false;
if (descriptor != null && descriptor.isContainer()) {
containerField = true;
}
//-- add object to stack so we don't potentially get into
//-- an endlessloop
if (_parents.search(object) >= 0) return;
_parents.push(object);
final boolean isNil = (object instanceof NilObject);
Class _class = null;
if (!isNil) {
_class = object.getClass();
if (_proxyInterfaces.size() > 0) {
boolean isProxy = false;
Class[] interfaces = _class.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
if (_proxyInterfaces.contains(interfaces[i].getName())) { isProxy = true; }
}
if (isProxy) { _class = _class.getSuperclass(); }
}
} else {
_class = ((NilObject) object).getClassDescriptor().getJavaClass();
}
boolean byteArray = false;
if (_class.isArray())
byteArray = (_class.getComponentType() == Byte.TYPE);
boolean atRoot = false;
if (descriptor == null) {
descriptor = new XMLFieldDescriptorImpl(_class, "root", null, null);
atRoot = true;
}
//-- calculate Object's name
String name = descriptor.getXMLName();
if (atRoot && _rootElement!=null)
name = _rootElement;
boolean autoNameByClass = false;
if (name == null) {
autoNameByClass = true;
name = _class.getName();
//-- remove package information from name
int idx = name.lastIndexOf('.');
if (idx >= 0) {
name = name.substring(idx+1);
}
//-- remove capitalization
name = getInternalContext().getXMLNaming().toXMLName(name);
}
//-- obtain the class descriptor
XMLClassDescriptor classDesc = null;
boolean saveType = false; /* flag for xsi:type */
if (object instanceof NilObject) {
classDesc = ((NilObject)object).getClassDescriptor();
} else if (_class == descriptor.getFieldType()) {
classDesc = (XMLClassDescriptor)descriptor.getClassDescriptor();
}
if (classDesc == null) {
//-- check for primitive or String, we need to use
//-- the special #isPrimitive method of this class
//-- so that we can check for the primitive wrapper
//-- classes
if (isPrimitive(_class) || byteArray) {
classDesc = _StringClassDescriptor;
//-- check to see if we need to save the xsi:type
//-- for this class
Class fieldType = descriptor.getFieldType();
if (_class != fieldType) {
while (fieldType.isArray()) {
fieldType = fieldType.getComponentType();
}
saveType = (!primitiveOrWrapperEquals(_class, fieldType));
}
}
else {
saveType = _class.isArray();
//-- save package information for use when searching
//-- for MarshalInfo classes
String className = _class.getName();
int idx = className.lastIndexOf(".");
String pkgName = null;
if (idx > 0) {
pkgName = className.substring(0,idx+1);
if (!_packages.contains(pkgName))
_packages.add(pkgName);
}
if (_marshalExtendedType) {
//-- Check to see if we can determine the class or
//-- ClassDescriptor from the type specified in the
//-- FieldHandler or from the current CDR state
if ((_class != descriptor.getFieldType()) || atRoot) {
saveType = true;
boolean containsDesc = false;
//-- if we're not at the root, check to see if we can resolve name.
//-- if we're at the root, the name will most likely be resolvable
//-- due to the validation step, so in most cases, if we are not
//-- using a mapping we need the xsi:type at the root
if (!atRoot) {
String nsURI = descriptor.getNameSpaceURI();
XMLClassDescriptor tmpDesc = null;
try {
tmpDesc = getResolver().resolveByXMLName(name, nsURI, null);
}
catch(ResolverException rx) {
//-- exception not important as we're simply
//-- testing to see if we can resolve during
//-- unmarshalling
if (LOG.isDebugEnabled()) {
LOG.debug("Error resolving", rx);
}
}
if (tmpDesc != null) {
Class tmpType = tmpDesc.getJavaClass();
if (tmpType == _class) {
containsDesc = (!tmpType.isInterface());
}
}
}
if (!containsDesc) {
//-- check for class mapping, we don't use the
//-- resolver directly because it will try to
//-- load a compiled descriptor, or introspect
//-- one
if (atRoot) {
if (_useXSITypeAtRoot) {
XMLMappingLoader ml = (XMLMappingLoader) getResolver().getMappingLoader();
if (ml != null) {
containsDesc = (ml.getDescriptor(_class.getName()) != null);
}
}
else {
//-- prevent xsi:type from appearing
//-- on root
containsDesc = true;
}
}
//-- The following logic needs to be expanded to use
//-- namespace -to- package mappings
if ((!containsDesc) && (pkgName == null)) {
//-- check to see if the class name is guessable
//-- from the xml name
classDesc = getClassDescriptor(_class);
if (classDesc != null) {
String tmpName = classDesc.getXMLName();
if (name.equals(tmpName))
saveType = false;
}
}
}
if (containsDesc) saveType = false;
}
// marshal as the actual type
if (classDesc == null)
classDesc = getClassDescriptor(_class);
} //-- end if (marshalExtendedType)
else {
// marshall as the base field type
_class = descriptor.getFieldType();
classDesc = getClassDescriptor(_class);
}
//-- If we are marshalling an array as the top
//-- level object, or if we run into a multi
//-- dimensional array, use the special
//-- ArrayDescriptor
if ((classDesc == null) && _class.isArray()) {
classDesc = new RootArrayDescriptor(_class);
if (atRoot) {
containerField = (!_asDocument);
}
}
} //-- end else not primitive
if (classDesc == null) {
//-- make sure we are allowed to marshal Object
if ((_class == Void.class) ||
(_class == Object.class) ||
(_class == Class.class)) {
throw new MarshalException
(MarshalException.BASE_CLASS_OR_VOID_ERR);
}
_parents.pop();
return;
}
}
//-- handle auto-naming by class
if (autoNameByClass) {
if (classDesc.getXMLName() != null) {
name = classDesc.getXMLName();
}
}
//-- at this point naming should be done, update
//-- MarshalState.xmlName if root element
if (atRoot) {
mstate._xmlName = name;
}
//------------------------------------------------/
//- Next few sections of code deal with xsi:type -/
//- prevention, if necessary -/
//------------------------------------------------/
//-- Allow user to prevent xsi:type
saveType = (saveType && (!_suppressXSIType));
//-- Suppress xsi:type for special types
if (saveType) {
//-- java.util.Enumeration and java.util.Date fix
if (descriptor.getHandler() instanceof DateFieldHandler)
saveType = false;
else if (descriptor.getHandler() instanceof EnumFieldHandler)
saveType = false;
else if (isNil)
saveType = false;
}
//-- Suppress 'xsi:type' attributes when Castor is able to infer
//-- the correct type during unmarshalling
if (saveType) {
// When the type of the instance of the field is not the
// type specified for the field, it might be necessary to
// store the type explicitly (using xsi:type) to avoid
// confusion during unmarshalling.
//
// However, it might be possible to use the XMLName of the
// instance rather than the XMLName of the field. If
// Castor could find back the type from the name of the
// element, there is no need to add an xsi:type.
//
// In order to do that, there is two conditions:
// 1. Castor should be able to find the right class to
// instantiate form the XMLName of the instance.
// 2. Castor should be sure than when using the XMLName of
// the instance, there is only one field wich will match
// that name (and that it is the current field)
// XML Name associated with the class we are marshalling
String xmlElementName = name;
String xmlNamespace = descriptor.getNameSpaceURI();
// We try to find if there is a XMLClassDescriptor associated
// with the XML name of this class
XMLClassDescriptor xmlElementNameClassDesc = null;
try {
xmlElementNameClassDesc = getResolver().resolveByXMLName(xmlElementName, null, null);
}
catch(ResolverException rx) {
//-- exception not important as we're simply
//-- testing to see if we can resolve during
//-- unmarshalling
if (LOG.isDebugEnabled()) {
LOG.debug("Error resolving " + xmlElementName, rx);
}
}
// Test if we are not dealing with a source generated vector
if ((xmlElementName != null) && (xmlElementNameClassDesc != null)) {
// More than one class can map to a given element name
try {
Iterator classDescriptorIter = getResolver().resolveAllByXMLName(xmlElementName, null, null);
for (; classDescriptorIter.hasNext();) {
xmlElementNameClassDesc = (XMLClassDescriptor) classDescriptorIter.next();
if (_class == xmlElementNameClassDesc.getJavaClass())
break;
//reset the classDescriptor --> none has been found
xmlElementNameClassDesc = null;
}
}
catch(ResolverException rx) {
if (LOG.isDebugEnabled()) {
LOG.debug("Error resolving " + xmlElementName, rx);
}
xmlElementNameClassDesc = null;
}
//make sure we only run into this logic if the classDescriptor
//is coming from a mapping file.
if (xmlElementNameClassDesc instanceof XMLClassDescriptorAdapter) {
// Try to find a field descriptor directly in the parent object
XMLClassDescriptor tempContaining = (XMLClassDescriptor)descriptor.getContainingClassDescriptor();
//--if no containing class descriptor
//--it means the container class could have been introspected
//--so no need to enter the logic
if (tempContaining != null) {
XMLFieldDescriptor fieldDescMatch =
tempContaining.getFieldDescriptor(xmlElementName, xmlNamespace, NodeType.Element);
// Try to find a field descriptor by inheritance in the parent object
InheritanceMatch[] matches =
searchInheritance(xmlElementName, null, tempContaining); // TODO: Joachim, _cdResolver);
if (matches.length == 1) {
boolean foundTheRightClass = ((xmlElementNameClassDesc != null) && (_class == xmlElementNameClassDesc.getJavaClass()));
boolean oneAndOnlyOneMatchedField
= ((fieldDescMatch != null) ||
(matches[0].parentFieldDesc == descriptor));
// Can we remove the xsi:type ?
if (foundTheRightClass && oneAndOnlyOneMatchedField) {
saveType = false;
//no name swapping for now
}
}//lengh is one
}
}//the classDesc comes from a mapping file
}
}//--- End of "if (saveType)"
//------------------------/
//- Namespace Management -/
//------------------------/
//-- Set a new namespace scoping
//-- Note: We still need to declare a new scope even if
//-- we are suppressing most namespaces. Certain elements
//-- like xsi:type and xsi:nil will require a namespace
//-- declaration and cannot be suppressed.
if (!atRoot) {
_namespaces = _namespaces.createNamespaces();
}
String nsPrefix = "";
String nsURI = "";
if (!_suppressNamespaces) {
//-- Must be done before any attributes are processed
//-- since attributes can be namespaced as well.
nsPrefix = descriptor.getNameSpacePrefix();
if (nsPrefix == null) nsPrefix = classDesc.getNameSpacePrefix();
nsURI = descriptor.getNameSpaceURI();
if (nsURI == null) nsURI = classDesc.getNameSpaceURI();
if ((nsURI == null) && (nsPrefix != null)) {
nsURI = _namespaces.getNamespaceURI(nsPrefix);
}
else if ((nsPrefix == null) && (nsURI != null)) {
nsPrefix = _namespaces.getNamespacePrefix(nsURI);
}
//-- declare namespace at this element scope?
if (nsURI != null) {
String defaultNamespace = _namespaces.getNamespaceURI("");
if ((nsPrefix == null) && (!nsURI.equals(defaultNamespace)))
{
if ((defaultNamespace == null) && atRoot) {
nsPrefix = "";
}
else nsPrefix = DEFAULT_PREFIX + (++NAMESPACE_COUNTER);
}
declareNamespace(nsPrefix, nsURI);
}
else {
nsURI = "";
//-- redeclare default namespace as empty
String defaultNamespace = _namespaces.getNamespaceURI("");
if ((defaultNamespace != null) && (!"".equals(defaultNamespace)))
_namespaces.addNamespace("", "");
}
}
//---------------------/
//- handle attributes -/
//---------------------/
AttributesImpl atts = new AttributesImpl();
//-- user defined attributes
if (atRoot) {
//-- declare xsi prefix if necessary
if (_topLevelAtts.getSize() > 0)
_namespaces.addNamespace(XSI_PREFIX, XSI_NAMESPACE);
for (int i = 0; i < _topLevelAtts.getSize(); i++) {
String localName = _topLevelAtts.getName(i);
String qName = localName;
String ns = "";
if (!_suppressNamespaces) {
ns = _topLevelAtts.getNamespace(i);
String prefix = null;
if ((ns != null) && (ns.length() > 0)) {
prefix = _namespaces.getNonDefaultNamespacePrefix(ns);
}
if ((prefix != null) && (prefix.length() > 0)) {
qName = prefix + ':' + qName;
}
if (ns == null) ns = "";
}
atts.addAttribute(ns, localName, qName, CDATA,
_topLevelAtts.getValue(i));
}
}
//----------------------------
//-- process attr descriptors
//----------------------------
int nestedAttCount = 0;
XMLFieldDescriptor[] nestedAtts = null;
XMLFieldDescriptor[] descriptors = null;
if ((!descriptor.isReference()) && (!isNil)) {
descriptors = classDesc.getAttributeDescriptors();
}
else {
// references don't have attributes
descriptors = NO_FIELD_DESCRIPTORS;
}
for (int i = 0; i < descriptors.length; i++) {
XMLFieldDescriptor attributeDescriptor = descriptors[i];
if (attributeDescriptor == null) {
continue;
}
String path = attributeDescriptor.getLocationPath();
if ((path != null) && (path.length() > 0)) {
//-- save for later processing
if (nestedAtts == null) {
nestedAtts = new XMLFieldDescriptor[descriptors.length - i];
}
nestedAtts[nestedAttCount] = attributeDescriptor;
nestedAttCount++;
} else {
processAttribute(object, attributeDescriptor, atts);
}
}
//-- handle ancestor nested attributes
if (mstate.nestedAttCount > 0) {
for (int i = 0; i < mstate.nestedAtts.length; i++) {
XMLFieldDescriptor attributeDescriptor = mstate.nestedAtts[i];
if (attributeDescriptor == null) {
continue;
}
String locationPath = attributeDescriptor.getLocationPath();
if (name.equals(locationPath)) {
// indicate that this 'nested' attribute has been processed
mstate.nestedAtts[i] = null;
// decrease number of unprocessed 'nested' attributes by one
mstate.nestedAttCount--;
processAttribute(mstate.getOwner(), attributeDescriptor, atts);
}
}
}
//-- Look for attributes in container fields,
//-- (also handle container in container)
if (!isNil) processContainerAttributes(object, classDesc, atts);
//-- xml:space
String attValue = descriptor.getXMLProperty(XMLFieldDescriptor.PROPERTY_XML_SPACE);
if (attValue != null) {
atts.addAttribute(Namespaces.XML_NAMESPACE, SPACE_ATTR, XML_SPACE_ATTR, CDATA, attValue);
}
//-- xml:lang
attValue = descriptor.getXMLProperty(XMLFieldDescriptor.PROPERTY_XML_LANG);
if (attValue != null) {
atts.addAttribute(Namespaces.XML_NAMESPACE, LANG_ATTR, XML_LANG_ATTR, CDATA, attValue);
}
//------------------/
//- Create element -/
//------------------/
//-- save xsi:type information, if necessary
if (saveType) {
//-- declare XSI namespace, if necessary
declareNamespace(XSI_PREFIX, XSI_NAMESPACE);
//-- calculate type name, either use class name or
//-- schema type name. If XMLClassDescriptor is introspected,
//-- or is the default XMLClassDescriptorImpl, then
//-- use java:classname, otherwise use XML name.
String typeName = classDesc.getXMLName();
//-- Check for introspection...
boolean introspected = false;
if (classDesc instanceof InternalXMLClassDescriptor)
introspected = ((InternalXMLClassDescriptor)classDesc).introspected();
else
introspected = Introspector.introspected(classDesc);
boolean useJavaPrefix = false;
if ((typeName == null) || introspected) {
typeName = JAVA_PREFIX + _class.getName();
useJavaPrefix = true;
}
else if (classDesc instanceof RootArrayDescriptor) {
typeName = JAVA_PREFIX + _class.getName();
useJavaPrefix = true;
}
else {
String dcn = classDesc.getClass().getName();
if (dcn.equals(XMLClassDescriptorImpl.class.getName())) {
typeName = JAVA_PREFIX + _class.getName();
useJavaPrefix = true;
}
else {
//-- calculate proper prefix
String tns = classDesc.getNameSpaceURI();
String prefix = null;
if ((tns != null) && (tns.length() > 0)) {
prefix = _namespaces.getNamespacePrefix(tns);
if ((prefix != null) && (prefix.length() > 0)) {
typeName = prefix + ':' + typeName;
}
}
}
}
//-- save type information
atts.addAttribute(XSI_NAMESPACE, TYPE_ATTR, XSI_TYPE, CDATA, typeName);
if (useJavaPrefix) {
//-- declare Java namespace, if necessary
declareNamespace("java", "http://java.sun.com");
}
}
if (isNil && !_suppressXSIType) {
//-- declare XSI namespace, if necessary
declareNamespace(XSI_PREFIX, XSI_NAMESPACE);
//-- add xsi:nil="true"
atts.addAttribute(XSI_NAMESPACE, NIL_ATTR, XSI_NIL_ATTR, CDATA, TRUE_VALUE);
}
//check if the value is a QName that needs to
//be resolved ({URI}value -> ns:value)
//This should be done BEFORE declaring the namespaces as attributes
//because we can declare new namespace during the QName resolution
String valueType = descriptor.getSchemaType();
if ((valueType != null) && (valueType.equals(QNAME_NAME))) {
object = resolveQName(object, descriptor);
}
String qName = null;
if (nsPrefix != null) {
int len = nsPrefix.length();
if (len > 0) {
StringBuffer sb = new StringBuffer(len+name.length()+1);
sb.append(nsPrefix);
sb.append(':');
sb.append(name);
qName = sb.toString();
}
else qName = name;
}
else qName = name;
Object firstNonNullValue = null;
int firstNonNullIdx = 0;
try {
if (!containerField) {
//-- isNillable?
if ((!isNil) && descriptor.isNillable()) {
XMLFieldDescriptor desc = classDesc.getContentDescriptor();
descriptors = classDesc.getElementDescriptors();
int descCount = descriptors.length;
boolean isNilContent = (descCount > 0) || (desc != null);
//-- check content descriptor for a valid value
if (desc != null) {
Object value = desc.getHandler().getValue(object);
if (value != null) {
isNilContent = false;
descCount = 0;
}
else if (desc.isNillable() && desc.isRequired()) {
isNilContent = false;
descCount = 0;
}
}
for (int i = 0; i < descCount; i++) {
desc = descriptors[i];
if (desc == null) continue;
Object value = desc.getHandler().getValue(object);
if (value != null) {
isNilContent = false;
firstNonNullIdx = i;
firstNonNullValue = value;
break;
}
else if (desc.isNillable() && desc.isRequired()) {
isNilContent = false;
firstNonNullIdx = i;
firstNonNullValue = new NilObject(classDesc, desc);
break;
}
}
if (isNilContent) {
declareNamespace(XSI_PREFIX, XSI_NAMESPACE);
atts.addAttribute(XSI_NAMESPACE, NIL_ATTR, XSI_NIL_ATTR, CDATA, TRUE_VALUE);
}
}
//-- declare all necesssary namespaces
_namespaces.sendStartEvents(handler);
//-- Make sure qName is not null
if (qName == null) {
//-- hopefully this never happens, but if it does, it means
//-- we have a bug in our naming logic
String err = "Error in deriving name for type: " +
_class.getName() + ", please report bug to: " +
"http://castor.exolab.org.";
throw new IllegalStateException(err);
}
handler.startElement(nsURI, name, qName, atts);
}
}
catch (org.xml.sax.SAXException sx) {
throw new MarshalException(sx);
}
//---------------------------------------
//-- process all child content, including
//-- text nodes + daughter elements
//---------------------------------------
Stack wrappers = null;
//----------------------
//-- handle text content
//----------------------
if (!isNil) {
XMLFieldDescriptor cdesc = null;
if (!descriptor.isReference()) {
cdesc = classDesc.getContentDescriptor();
}
if (cdesc != null) {
Object obj = null;
try {
obj = cdesc.getHandler().getValue(object);
}
catch(IllegalStateException ise) {
LOG.warn("Error getting value from: " + object, ise);
}
if (obj != null) {
//-- <Wrapper>
//-- handle XML path
String path = cdesc.getLocationPath();
String currentLoc = null;
if (path != null) {
_attributes.clear();
if (wrappers == null) {
wrappers = new SafeStack();
}
try {
while (path != null) {
String elemName = null;
int idx = path.indexOf('/');
if (idx > 0) {
elemName = path.substring(0, idx);
path = path.substring(idx+1);
}
else {
elemName = path;
path = null;
}
//-- save current location without namespace
//-- information for now.
if (currentLoc == null)
currentLoc = elemName;
else
currentLoc = currentLoc + "/" + elemName;
String elemQName = elemName;
if ((nsPrefix != null) && (nsPrefix.length() > 0)) {
elemQName = nsPrefix + ':' + elemName;
}
wrappers.push(new WrapperInfo(elemName, elemQName, currentLoc));
_attributes.clear();
if (nestedAttCount > 0) {
for (int na = 0; na < nestedAtts.length; na++) {
if (nestedAtts[na] == null) continue;
String tmpPath = nestedAtts[na].getLocationPath();
if (tmpPath.equals(currentLoc)) {
processAttribute(object, nestedAtts[na],_attributes);
nestedAtts[na] = null;
--nestedAttCount;
}
}
}
handler.startElement(nsURI, elemName, elemQName, _attributes);
}
}
catch(SAXException sx) {
throw new MarshalException(sx);
}
}
//-- </Wrapper>
char[] chars = null;
Class objType = obj.getClass();
if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) {
//-- handle base64/hexbinary content
final String schemaType = descriptor.getSchemaType();
if (HexDecoder.DATA_TYPE.equals(schemaType)) {
chars = new String(HexDecoder.encode((byte[]) obj)).toCharArray();
} else {
chars = Base64Encoder.encode((byte[]) obj);
}
} else {
//-- all other types
String str = obj.toString();
if ((str != null) && (str.length() > 0)) {
chars = str.toCharArray();
}
}
if ((chars != null) && (chars.length > 0)) {
try {
handler.characters(chars, 0, chars.length);
}
catch(org.xml.sax.SAXException sx) {
throw new MarshalException(sx);
}
}
}
}
//-- element references
else if (descriptor.isReference()) {
Object id = getObjectID(object);
if (id != null) {
char[] chars = id.toString().toCharArray();
try {
handler.characters(chars, 0, chars.length);
}
catch(org.xml.sax.SAXException sx) {
throw new MarshalException(sx);
}
}
}
// special case for byte[]
else if (byteArray) {
//-- Base64Encoding / HexBinary
String schemaType = descriptor.getSchemaType();
String componentType = descriptor.getComponentType();
char[] chars = new char[0];
if ((descriptor.isMultivalued() && HexDecoder.DATA_TYPE.equals(componentType)) ||
HexDecoder.DATA_TYPE.equals(schemaType)) {
chars = new String(HexDecoder.encode((byte[]) object)).toCharArray();
} else {
chars = Base64Encoder.encode((byte[]) object);
}
try {
handler.characters(chars, 0, chars.length);
} catch (org.xml.sax.SAXException sx) {
throw new MarshalException(sx);
}
}
/* special case for Strings and primitives */
else if (isPrimitive(_class)) {
char[] chars;
if (_class == java.math.BigDecimal.class) {
chars = convertBigDecimalToString(object).toCharArray();
} else {
chars = object.toString().toCharArray();
}
try {
handler.characters(chars,0,chars.length);
}
catch(org.xml.sax.SAXException sx) {
throw new MarshalException(sx);
}
}
else if (isEnum(_class)) {
char[] chars = object.toString().toCharArray();
try {
handler.characters(chars,0,chars.length);
}
catch(org.xml.sax.SAXException sx) {
throw new MarshalException(sx);
}
}
}
//---------------------------
//-- handle daughter elements
//---------------------------
if (isNil || descriptor.isReference()) {
descriptors = NO_FIELD_DESCRIPTORS;
}
else {
descriptors = classDesc.getElementDescriptors();
}
++depth;
//-- marshal elements
for (int i = firstNonNullIdx; i < descriptors.length; i++) {
XMLFieldDescriptor elemDescriptor = descriptors[i];
Object obj = null;
boolean nil = false;
//-- used previously cached value?
if ((i == firstNonNullIdx) && (firstNonNullValue != null)) {
obj = firstNonNullValue;
} else {
//-- obtain value from handler
try {
obj = elemDescriptor.getHandler().getValue(object);
} catch (IllegalStateException ise) {
LOG.warn("Error marshalling " + object, ise);
continue;
}
}
if (obj == null
|| (obj instanceof Enumeration && !((Enumeration) obj).hasMoreElements())) {
if (elemDescriptor.isNillable() && (elemDescriptor.isRequired())) {
nil = true;
} else {
continue;
}
}
//-- handle XML path
String path = elemDescriptor.getLocationPath();
String currentLoc = null;
//-- Wrapper/Location cleanup
if (wrappers != null) {
try {
while (!wrappers.empty()) {
WrapperInfo wInfo = (WrapperInfo)wrappers.peek();
if (path != null) {
if (wInfo.location.equals(path)) {
path = null;
break;
}
else if (path.startsWith(wInfo.location + "/")) {
path = path.substring(wInfo.location.length()+1);
currentLoc = wInfo.location;
break;
}
}
handler.endElement(nsURI, wInfo.localName, wInfo.qName);
wrappers.pop();
}
}
catch(SAXException sx) {
throw new MarshalException(sx);
}
}
if (path != null) {
_attributes.clear();
if (wrappers == null) {
wrappers = new SafeStack();
}
try {
while (path != null) {
String elemName = null;
int idx = path.indexOf('/');
if (idx > 0) {
elemName = path.substring(0, idx);
path = path.substring(idx+1);
}
else {
elemName = path;
path = null;
}
//-- save current location without namespace
//-- information for now.
if (currentLoc == null)
currentLoc = elemName;
else
currentLoc = currentLoc + "/" + elemName;
String elemQName = elemName;
if ((nsPrefix != null) && (nsPrefix.length() > 0)) {
elemQName = nsPrefix + ':' + elemName;
}
wrappers.push(new WrapperInfo(elemName, elemQName, currentLoc));
_attributes.clear();
if (nestedAttCount > 0) {
for (int na = 0; na < nestedAtts.length; na++) {
if (nestedAtts[na] == null) continue;
String tmpPath = nestedAtts[na].getLocationPath();
if (tmpPath.equals(currentLoc)) {
processAttribute(object, nestedAtts[na],_attributes);
nestedAtts[na] = null;
--nestedAttCount;
}
}
}
handler.startElement(nsURI, elemName, elemQName, _attributes);
}
}
catch(SAXException sx) {
throw new MarshalException(sx);
}
}
if (nil) {
obj = new NilObject(classDesc, elemDescriptor);
}
final Class type = obj.getClass();
MarshalState myState = mstate.createMarshalState(object, name);
myState.nestedAtts = nestedAtts;
myState.nestedAttCount = nestedAttCount;
//-- handle byte arrays
if (type.isArray() && (type.getComponentType() == Byte.TYPE)) {
marshal(obj, elemDescriptor, handler, myState);
} else if (type.isArray() && elemDescriptor.isDerivedFromXSList()) {
Object buffer = processXSListType(obj, elemDescriptor);
String elemName = elemDescriptor.getXMLName();
String elemQName = elemName;
if ((nsPrefix != null) && (nsPrefix.length() > 0)) {
elemQName = nsPrefix + ':' + elemName;
}
char[] chars = buffer.toString().toCharArray();
try {
handler.startElement(nsURI, elemName, elemQName, _attributes);
handler.characters(chars,0,chars.length);
handler.endElement(nsURI, elemName, elemQName);
}
catch(org.xml.sax.SAXException sx) {
throw new MarshalException(sx);
}
}
//-- handle all other collection types
else if (isCollection(type)) {
boolean processCollection = true;
if (_saveMapKeys) {
MapHandler mapHandler = MapHandlers.getHandler(type);
if (mapHandler != null) {
processCollection = false;
MapItem item = new MapItem();
Enumeration keys = mapHandler.keys(obj);
while (keys.hasMoreElements()) {
item.setKey(keys.nextElement());
item.setValue(mapHandler.get(obj, item.getKey()));
marshal(item, elemDescriptor, handler, myState);
}
}
}
if (processCollection) {
CollectionHandler colHandler = getCollectionHandler(type);
Enumeration enumeration = colHandler.elements(obj);
while (enumeration.hasMoreElements()) {
Object item = enumeration.nextElement();
if (item != null) {
marshal(item, elemDescriptor, handler, myState);
}
}
}
}
//-- otherwise just marshal object as is
else {
marshal(obj, elemDescriptor, handler, myState);
}
if (nestedAttCount > 0) {
nestedAttCount = myState.nestedAttCount;
}
}
//-- Wrapper/Location cleanup for elements
if (wrappers != null) {
try {
while (!wrappers.empty()) {
WrapperInfo wInfo = (WrapperInfo) wrappers.peek();
boolean popStack = true;
if (nestedAttCount > 0) {
for (int na = 0; na < nestedAtts.length; na++) {
// TODO[LL]: refactor to avoid check against null
if (nestedAtts[na] == null) continue;
String nestedAttributePath = nestedAtts[na].getLocationPath();
if (nestedAttributePath.startsWith(wInfo.location + "/")) {
popStack = false;
break;
}
}
}
if (popStack) {
handler.endElement(nsURI, wInfo.localName, wInfo.qName);
wrappers.pop();
} else {
break;
}
}
}
catch(SAXException sx) {
throw new MarshalException(sx);
}
}
// TODO: Handling additional attributes at the end causes elements to be marshalled in the wrong
// order when element 'text' is null, but their attribute value is not null. Can this be fixed
// to process element attributes even when the element text value is null?
if (wrappers != null && !wrappers.isEmpty()) {
dealWithNestedAttributesNested(object, handler, nsPrefix, nsURI,
nestedAttCount, nestedAtts, wrappers);
}
dealWithNestedAttributes(object, handler, nsPrefix, nsURI,
nestedAttCount, nestedAtts, new SafeStack());
//-- finish element
try {
if (!containerField) {
handler.endElement(nsURI, name, qName);
//-- undeclare all necesssary namespaces
_namespaces.sendEndEvents(handler);
}
}
catch(org.xml.sax.SAXException sx) {
throw new MarshalException(sx);
}
--depth;
_parents.pop();
if (!atRoot) _namespaces = _namespaces.getParent();
//-- notify listener of post marshal
if (_marshalListener != null)
_marshalListener.postMarshal(object);
} //-- void marshal(DocumentHandler)
private void dealWithNestedAttributes(Object object,
ContentHandler handler, String nsPrefix, String nsURI,
int nestedAttCount, XMLFieldDescriptor[] nestedAtts, Stack wrappers)
throws MarshalException {
//-- Handle any additional attribute locations that were
//-- not handled when dealing with wrapper elements
if (nestedAttCount > 0) {
for (int i = 0; i < nestedAtts.length; i++) {
if (nestedAtts[i] == null) continue;
String path = nestedAtts[i].getLocationPath();
String currentLoc = null;
//-- Make sure attribute has value before continuing
//-- We really could use a FieldHandler#hasValue()
//-- method (since sometimes getValue() methods may
//-- be expensive and we don't always want to call it
//-- multiple times)
if (nestedAtts[i].getHandler().getValue(object) == null) {
nestedAtts[i] = null;
-- nestedAttCount;
continue;
}
try {
while (path != null) {
int idx = path.indexOf('/');
String elemName = null;
if (idx > 0) {
elemName = path.substring(0,idx);
path = path.substring(idx+1);
}
else {
elemName = path;
path = null;
}
if (currentLoc == null)
currentLoc = elemName;
else
currentLoc = currentLoc + "/" + elemName;
String elemQName = elemName;
if ((nsPrefix != null) && (nsPrefix.length() > 0)) {
elemQName = nsPrefix + ':' + elemName;
}
wrappers.push(new WrapperInfo(elemName, elemQName, null));
_attributes.clear();
if (path == null) {
processAttribute(object, nestedAtts[i],_attributes);
nestedAtts[i] = null;
--nestedAttCount;
}
if (nestedAttCount > 0) {
for (int na = i+1; na < nestedAtts.length; na++) {
if (nestedAtts[na] == null) continue;
String tmpPath = nestedAtts[na].getLocationPath();
if (tmpPath.equals(currentLoc)) {
processAttribute(object, nestedAtts[na],_attributes);
nestedAtts[na] = null;
--nestedAttCount;
}
}
}
handler.startElement(nsURI, elemName, elemQName, _attributes);
}
while (!wrappers.empty()) {
WrapperInfo wInfo = (WrapperInfo)wrappers.pop();
handler.endElement(nsURI, wInfo.localName, wInfo.qName);
}
} catch (Exception e) {
throw new MarshalException(e);
}
}
} // if (nestedAttCount > 0)
}
private void dealWithNestedAttributesNested(Object object,
ContentHandler handler, String nsPrefix, String nsURI,
int nestedAttCount, XMLFieldDescriptor[] nestedAtts, Stack wrappers)
throws MarshalException {
//-- Handle any additional attribute locations that were
//-- not handled when dealing with wrapper elements
WrapperInfo wrapperInfo = (WrapperInfo) wrappers.peek();
String currentLocation = wrapperInfo.location;
if (nestedAttCount > 0) {
for (int i = 0; i < nestedAtts.length; i++) {
if (nestedAtts[i] == null) continue;
String nestedAttributePath = nestedAtts[i].getLocationPath();
if (!nestedAttributePath.startsWith(currentLocation + "/")) {
continue;
}
nestedAttributePath = nestedAttributePath.substring(wrapperInfo.location.length() + 1);
String currentLoc = currentLocation;
//-- Make sure attribute has value before continuing
//-- We really could use a FieldHandler#hasValue()
//-- method (since sometimes getValue() methods may
//-- be expensive and we don't always want to call it
//-- multiple times)
if (nestedAtts[i].getHandler().getValue(object) == null) {
nestedAtts[i] = null;
-- nestedAttCount;
continue;
}
try {
while (nestedAttributePath != null) {
int idx = nestedAttributePath.indexOf('/');
String elemName = null;
if (idx > 0) {
elemName = nestedAttributePath.substring(0,idx);
nestedAttributePath = nestedAttributePath.substring(idx+1);
}
else {
elemName = nestedAttributePath;
nestedAttributePath = null;
}
if (currentLoc == null)
currentLoc = elemName;
else
currentLoc = currentLoc + "/" + elemName;
String elemQName = elemName;
if ((nsPrefix != null) && (nsPrefix.length() > 0)) {
elemQName = nsPrefix + ':' + elemName;
}
wrappers.push(new WrapperInfo(elemName, elemQName, null));
_attributes.clear();
if (nestedAttributePath == null) {
processAttribute(object, nestedAtts[i],_attributes);
nestedAtts[i] = null;
--nestedAttCount;
}
if (nestedAttCount > 0) {
for (int na = i+1; na < nestedAtts.length; na++) {
if (nestedAtts[na] == null) continue;
String tmpPath = nestedAtts[na].getLocationPath();
if (tmpPath.equals(currentLoc)) {
processAttribute(object, nestedAtts[na],_attributes);
nestedAtts[na] = null;
--nestedAttCount;
}
}
}
handler.startElement(nsURI, elemName, elemQName, _attributes);
}
while (!wrappers.empty()) {
WrapperInfo wInfo = (WrapperInfo)wrappers.pop();
handler.endElement(nsURI, wInfo.localName, wInfo.qName);
}
} catch (Exception e) {
throw new MarshalException(e);
}
}
} // if (nestedAttCount > 0)
}
/**
* Converts a {@link BigDecimal} instance value to its String representation. This
* method will take into into account the Java version number, as the semantics of
* BigDecimal.toString() have changed between Java 1.4 and Java 5.0 and above.
* @param object The {@link BigDecimal} instance
* @return The String representation of the {@link BigDecimal} instance
* @throws MarshalException If invocation of BigDecimal#toPlainString() fails.
*/
private String convertBigDecimalToString(Object object) throws MarshalException {
String stringValue;
float javaVersion = Float.parseFloat(System.getProperty("java.specification.version"));
if (javaVersion >= 1.5) {
// as of Java 5.0 and above, BigDecimal.toPlainString() should be used.
// TODO: reconsider if we start using BigDecimal for XSTypes that can hold scientific values
Method method;
try {
method = java.math.BigDecimal.class.getMethod("toPlainString", (Class[]) null);
stringValue = (String) method.invoke(object, (Object[]) null);
} catch (Exception e) {
LOG.error("Problem accessing java.math.BigDecimal.toPlainString().", e);
throw new MarshalException("Problem accessing java.math.BigDecimal.toPlainString().", e);
}
} else {
// use BigDecimal.toString() with Java 1.4 and below
stringValue = object.toString();
}
return stringValue;
}
/**
* Retrieves the ID for the given Object
*
* @param object the Object to retrieve the ID for
* @return the ID for the given Object
**/
private Object getObjectID(Object object)
throws MarshalException
{
if (object == null) return null;
Object id = null;
XMLClassDescriptor cd = getClassDescriptor(object.getClass());
String err = null;
if (cd != null) {
XMLFieldDescriptor fieldDesc
= (XMLFieldDescriptor) cd.getIdentity();
if (fieldDesc != null) {
FieldHandler fieldHandler = fieldDesc.getHandler();
if (fieldHandler != null) {
try {
id = fieldHandler.getValue(object);
}
catch(IllegalStateException ise) {
err = ise.toString();
}
}//fieldHandler != null
else {
err = "FieldHandler for Identity descriptor is null.";
}
}//fieldDesc != null
else err = "No identity descriptor available";
}//cd!=null
else {
err = "Unable to resolve ClassDescriptor.";
}
if (err != null) {
String errMsg = "Unable to resolve ID for instance of class '";
errMsg += object.getClass().getName();
errMsg += "' due to the following error: ";
throw new MarshalException(errMsg + err);
}
return id;
} //-- getID
/**
* Declares the given namespace, if not already in scope
*
* @param nsPrefix the namespace prefix
* @param nsURI the namespace URI to declare
* @return true if the namespace was not in scope and was
* sucessfully declared, other false
**/
private boolean declareNamespace(String nsPrefix, String nsURI)
{
boolean declared = false;
//-- make sure it's not already declared...
if ( (nsURI != null) && (nsURI.length() != 0)) {
String tmpURI = _namespaces.getNamespaceURI(nsPrefix);
if ((tmpURI != null) && (tmpURI.equals(nsURI))) {
return declared;
}
String tmpPrefix = _namespaces.getNamespacePrefix(nsURI);
if ((tmpPrefix == null) || (!tmpPrefix.equals(nsPrefix))) {
_namespaces.addNamespace(nsPrefix, nsURI);
declared = true;
}
}
return declared;
} //-- declareNamespace
/**
* Sets the PrintWriter used for logging
* @param printWriter the PrintWriter to use for logging
**/
public void setLogWriter(final PrintWriter printWriter) { }
/**
* Sets the encoding for the serializer. Note that this method
* cannot be called if you've passed in your own DocumentHandler.
*
* @param encoding the encoding to set
**/
public void setEncoding(String encoding) {
if (_serializer != null) {
if (_format == null) {
_format = getInternalContext().getOutputFormat();
}
_format.setEncoding(encoding);
//-- reset output format, this needs to be done
//-- any time a change occurs to the format.
_serializer.setOutputFormat( _format );
try {
//-- Due to a Xerces Serializer bug that doesn't allow declaring
//-- multiple prefixes to the same namespace, we use the old
//-- DocumentHandler format and process namespaces ourselves
_handler = new DocumentHandlerAdapter(_serializer.asDocumentHandler());
}
catch (java.io.IOException iox) {
//-- we can ignore this exception since it shouldn't
//-- happen. If _serializer is not null, it means
//-- we've already called this method sucessfully
//-- in the Marshaller() constructor
if (LOG.isDebugEnabled()) {
LOG.debug("Error setting encoding to " + encoding, iox);
}
}
}
else {
String error = "encoding cannot be set if you've passed in "+
"your own DocumentHandler";
throw new IllegalStateException(error);
}
} //-- setEncoding
/**
* Sets the value for the xsi:noNamespaceSchemaLocation attribute.
* When set, this attribute will appear on the root element
* of the marshalled document.
*
* @param schemaLocation the URI location of the schema
* to which the marshalled document is an instance of.
**/
public void setNoNamespaceSchemaLocation(String schemaLocation) {
if (schemaLocation == null) {
//-- remove if necessary
//-- to be added later.
}
else {
_topLevelAtts.setAttribute(XSI_NO_NAMESPACE_SCHEMA_LOCATION,
schemaLocation, XSI_NAMESPACE);
}
} //-- setNoNamespaceSchemaLocation
/**
* Sets the value for the xsi:schemaLocation attribute.
* When set, this attribute will appear on the root element
* of the marshalled document.
*
* @param schemaLocation the URI location of the schema
* to which the marshalled document is an instance of.
**/
public void setSchemaLocation(String schemaLocation) {
if (schemaLocation == null) {
//-- remove if necessary
//-- to be added later.
}
else {
_topLevelAtts.setAttribute(XSI_SCHEMA_LOCATION,
schemaLocation, XSI_NAMESPACE);
}
} //-- setSchemaLocation
/**
* Sets whether or not namespaces are output. By default
* the Marshaller will output namespace declarations and
* prefix elements and attributes with their respective
* namespace prefix. This method can be used to prevent
* the usage of namespaces.
*
* @param suppressNamespaces a boolean that when true
* will prevent namespaces from being output.
*/
public void setSuppressNamespaces(boolean suppressNamespaces) {
_suppressNamespaces = suppressNamespaces;
} //-- setSuppressNamespaces
/**
* Sets whether or not the xsi:type attribute should appear
* on the marshalled document.
*
* @param suppressXSIType a boolean that when true will prevent
* xsi:type attribute from being used in the marshalling process.
*/
public void setSuppressXSIType(boolean suppressXSIType) {
_suppressXSIType = suppressXSIType;
} //-- setSuppressXSIType
/**
* Sets whether or not to output the xsi:type at the root
* element. This is usually needed when the root element
* type cannot be determined by the element name alone.
* By default xsi:type will not be output on the root
* element.
*
* @param useXSITypeAtRoot a boolean that when true indicates
* that the xsi:type should be output on the root element.
*/
public void setUseXSITypeAtRoot(boolean useXSITypeAtRoot) {
_useXSITypeAtRoot = useXSITypeAtRoot;
} //-- setUseXSITypeAtRoot
/**
* Finds and returns an XMLClassDescriptor for the given class. If
* a XMLClassDescriptor could not be found, this method will attempt to
* create one automatically using reflection.
* @param _class the Class to get the XMLClassDescriptor for
* @exception MarshalException when there is a problem
* retrieving or creating the XMLClassDescriptor for the given class
**/
private XMLClassDescriptor getClassDescriptor(Class _class)
throws MarshalException
{
XMLClassDescriptor classDesc = null;
try {
if (!isPrimitive(_class))
classDesc = (XMLClassDescriptor) getResolver().resolve(_class);
}
catch(ResolverException rx) {
Throwable actual = rx.getCause();
if (actual instanceof MarshalException) {
throw (MarshalException)actual;
}
if (actual != null) {
throw new MarshalException(actual);
}
throw new MarshalException(rx);
}
if (classDesc != null)
classDesc = new InternalXMLClassDescriptor(classDesc);
return classDesc;
} //-- getClassDescriptor
/**
* Processes the attribute associated with the given attDescriptor and parent
* object.
*
* @param atts the SAX attribute list to add the attribute to
*/
private void processAttribute
(Object object, XMLFieldDescriptor attDescriptor, AttributesImpl atts)
throws MarshalException
{
if (attDescriptor == null) return;
//-- process Namespace nodes from Object Model,
//-- if necessary.
if (attDescriptor.getNodeType() == NodeType.Namespace) {
if (!_suppressNamespaces) {
Object map = attDescriptor.getHandler().getValue(object);
MapHandler mapHandler = MapHandlers.getHandler(map);
if (mapHandler != null) {
Enumeration keys = mapHandler.keys(map);
while (keys.hasMoreElements()) {
Object key = keys.nextElement();
Object val = mapHandler.get(map, key);
declareNamespace(key.toString(), val.toString());
}
}
}
return;
}
String localName = attDescriptor.getXMLName();
String qName = localName;
//-- handle attribute namespaces
String namespace = "";
if (!_suppressNamespaces) {
namespace = attDescriptor.getNameSpaceURI();
if ((namespace != null) && (namespace.length() > 0)) {
String prefix = attDescriptor.getNameSpacePrefix();
if ((prefix == null) || (prefix.length() == 0))
prefix = _namespaces.getNonDefaultNamespacePrefix(namespace);
if ((prefix == null) || (prefix.length() == 0)) {
//-- automatically create namespace prefix?
prefix = DEFAULT_PREFIX + (++NAMESPACE_COUNTER);
}
declareNamespace(prefix, namespace);
qName = prefix + ':' + qName;
}
else namespace = "";
}
Object value = null;
try {
value = attDescriptor.getHandler().getValue(object);
}
catch(IllegalStateException ise) {
LOG.warn("Error getting value from " + object, ise);
return;
}
//-- handle IDREF(S)
if (attDescriptor.isReference() && (value != null)) {
if (attDescriptor.isMultivalued()) {
Enumeration enumeration = null;
if (value instanceof Enumeration) {
enumeration = (Enumeration)value;
}
else {
CollectionHandler colHandler = null;
try {
colHandler = CollectionHandlers.getHandler(value.getClass());
}
catch(MappingException mx) {
throw new MarshalException(mx);
}
enumeration = colHandler.elements(value);
}
if (enumeration.hasMoreElements()) {
StringBuffer sb = new StringBuffer();
for (int v = 0; enumeration.hasMoreElements(); v++) {
if (v > 0) sb.append(' ');
sb.append(getObjectID(enumeration.nextElement()).toString());
}
value = sb;
}
else value = null;
}
else {
value = getObjectID(value);
}
}
//-- handle multi-value attributes
else if (attDescriptor.isMultivalued() && (value != null)) {
value = processXSListType(value, attDescriptor);
}
else if (value != null) {
//-- handle hex/base64 content
Class objType = value.getClass();
if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) {
value = encodeBinaryData(value, attDescriptor.getSchemaType());
}
}
if (value != null) {
//check if the value is a QName that needs to
//be resolved ({URI}value -> ns:value).
String valueType = attDescriptor.getSchemaType();
if ((valueType != null) && (valueType.equals(QNAME_NAME)))
value = resolveQName(value, attDescriptor);
atts.addAttribute(namespace, localName, qName, CDATA, value.toString());
}
} //-- processAttribute
private Object processXSListType(final Object value, XMLFieldDescriptor descriptor)
throws MarshalException {
Object returnValue = null;
Enumeration enumeration = null;
if (value instanceof Enumeration) {
enumeration = (Enumeration)value;
}
else {
CollectionHandler colHandler = null;
try {
colHandler = CollectionHandlers.getHandler(value.getClass());
}
catch(MappingException mx) {
throw new MarshalException(mx);
}
enumeration = colHandler.elements(value);
}
if (enumeration.hasMoreElements()) {
StringBuffer sb = new StringBuffer();
for (int v = 0; enumeration.hasMoreElements(); v++) {
if (v > 0) {
sb.append(' ');
}
Object collectionValue = enumeration.nextElement();
//-- handle hex/base64 content
Class objType = collectionValue.getClass();
if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) {
collectionValue = encodeBinaryData(collectionValue, descriptor.getComponentType());
}
sb.append(collectionValue.toString());
}
returnValue = sb;
}
return returnValue;
}
/**
* Encode binary data.
* @param valueToEncode The binary data to encode.
* @param componentType The XML schema component type.
* @return Encoded binary data in {@link String} form.
*/
private Object encodeBinaryData(final Object valueToEncode,
final String componentType) {
String encodedValue;
if (HexDecoder.DATA_TYPE.equals(componentType)) {
encodedValue = HexDecoder.encode((byte[]) valueToEncode);
} else {
encodedValue = new String(Base64Encoder.encode((byte[]) valueToEncode));
}
return encodedValue;
}
/**
* Processes the attributes for container objects
*
* @param target the object currently being marshalled.
* @param classDesc the XMLClassDescriptor for the target object
* @param atts the SAX attributes list to add attributes to
*/
private void processContainerAttributes
(Object target, XMLClassDescriptor classDesc, AttributesImpl atts)
throws MarshalException
{
if (classDesc instanceof XMLClassDescriptorImpl) {
if (!((XMLClassDescriptorImpl)classDesc).hasContainerFields())
return;
}
XMLFieldDescriptor[] elemDescriptors = classDesc.getElementDescriptors();
for (int i = 0; i < elemDescriptors.length; i++) {
if (elemDescriptors[i] == null) continue;
if (!elemDescriptors[i].isContainer()) continue;
processContainerAttributes(target, elemDescriptors[i], atts);
}
} //-- processContainerAttributes
/**
* Processes the attributes for container objects.
*
* @param target the object currently being marshalled.
* @param containerFieldDesc the XMLFieldDescriptor for the containter to process
* @param atts the SAX attributes list to add any necessary attributes to.
* @throws MarshalException If there's a problem marshalling the attribute(s).
*/
private void processContainerAttributes
(final Object target, final XMLFieldDescriptor containerFieldDesc,
final AttributesImpl atts)
throws MarshalException {
if (target.getClass().isArray()) {
int length = Array.getLength(target);
for (int j = 0; j < length; j++) {
Object item = Array.get(target, j);
if (item != null) {
processContainerAttributes(item, containerFieldDesc, atts);
}
}
return;
} else if (target instanceof Enumeration) {
Enumeration enumeration = (Enumeration) target;
while (enumeration.hasMoreElements()) {
Object item = enumeration.nextElement();
if (item != null) {
processContainerAttributes(item, containerFieldDesc, atts);
}
}
return;
}
Object containerObject = containerFieldDesc.getHandler().getValue(target);
if (containerObject == null) {
return;
}
XMLClassDescriptor containerClassDesc
= (XMLClassDescriptor) containerFieldDesc.getClassDescriptor();
if (containerClassDesc == null) {
containerClassDesc = getClassDescriptor(containerFieldDesc.getFieldType());
if (containerClassDesc == null) {
return;
}
}
// Look for attributes
XMLFieldDescriptor[] attrDescriptors = containerClassDesc.getAttributeDescriptors();
for (int idx = 0; idx < attrDescriptors.length; idx++) {
if (attrDescriptors[idx] == null) {
continue;
}
if (attrDescriptors[idx].getLocationPath() == null
|| attrDescriptors[idx].getLocationPath().length() == 0) {
processAttribute(containerObject, attrDescriptors[idx], atts);
}
}
// recursively process containers
processContainerAttributes(containerObject, containerClassDesc, atts);
} //-- processContainerAttributes
/**
* Resolve a QName value ({URI}value) by declaring a namespace after
* having retrieved the prefix.
*/
private Object resolveQName(Object value, XMLFieldDescriptor fieldDesc) {
if ( (value == null) || !(value instanceof String))
return value;
if (!(fieldDesc instanceof XMLFieldDescriptorImpl))
return value;
String result = (String)value;
String nsURI = null;
int idx = -1;
if ((result.length() > 0) && (result.charAt(0) == '{')) {
idx = result.indexOf('}');
if (idx <= 0) {
String err = "Bad QName value :'"+result+"', it should follow the pattern '{URI}value'";
throw new IllegalArgumentException(err);
}
nsURI = result.substring(1, idx);
}
else return value;
String prefix = ((XMLFieldDescriptorImpl)fieldDesc).getQNamePrefix();
//no prefix provided, check if one has been previously defined
if (prefix == null)
prefix = _namespaces.getNamespacePrefix(nsURI);
//if still no prefix, use a naming algorithm (ns+counter).
if (prefix == null)
prefix = DEFAULT_PREFIX+(++NAMESPACE_COUNTER);
result = (prefix.length() != 0)?prefix+":"+result.substring(idx+1):result.substring(idx+1);
declareNamespace(prefix, nsURI);
return result;
}
private void validate(final Object object) throws ValidationException {
if (_validate) {
//-- we must have a valid element before marshalling
Validator validator = new Validator();
ValidationContext context = new ValidationContext();
context.setInternalContext(getInternalContext());
// context.setConfiguration(_config);
// context.setResolver(_cdResolver);
validator.validate(object, context);
}
}
/**
* Returns the value of the given Castor XML-specific property.
* @param name Qualified name of the CASTOR XML-specific property.
* @return The current value of the given property.
* @since 1.1.2
*/
public String getProperty(final String name) {
return getInternalContext().getStringProperty(name);
}
/**
* Sets a custom value of a given Castor XML-specific property.
* @param name Name of the Castor XML property
* @param value Custom value to set.
* @since 1.1.2
*/
public void setProperty(final String name, final String value) {
getInternalContext().setProperty(name, value);
deriveProperties();
}
/**
* Inner-class used for handling wrapper elements
* and locations.
*/
static class WrapperInfo {
String localName = null;
String qName = null;
String location = null;
WrapperInfo(String localName, String qName, String location) {
this.localName = localName;
this.qName = qName;
this.location = location;
}
}
static class MarshalState {
String xpath = null;
XMLFieldDescriptor[] nestedAtts = null;
int nestedAttCount = 0;
private MarshalState _parent = null;
private Object _owner = null;
private String _xmlName = null;
MarshalState(Object owner, String xmlName) {
if (owner == null) {
String err = "The argument 'owner' must not be null";
throw new IllegalArgumentException(err);
}
if (xmlName == null) {
String err = "The argument 'xmlName' must not be null";
throw new IllegalArgumentException(err);
}
_owner = owner;
_xmlName = xmlName;
}
MarshalState createMarshalState(Object owner, String xmlName) {
MarshalState ms = new MarshalState(owner, xmlName);
ms._parent = this;
return ms;
}
String getXPath() {
if (xpath == null) {
if (_parent != null) {
xpath = _parent.getXPath() + "/" + _xmlName;
}
else {
xpath = _xmlName;
}
}
return xpath;
}
Object getOwner() {
return _owner;
}
MarshalState getParent() {
return _parent;
}
}
/**
* A wrapper for a "Nil" object
*
*/
public static class NilObject {
private XMLClassDescriptor _classDesc = null;
private XMLFieldDescriptor _fieldDesc = null;
NilObject(XMLClassDescriptor classDesc, XMLFieldDescriptor fieldDesc) {
_classDesc = classDesc;
_fieldDesc = fieldDesc;
}
/**
* Returns the associated XMLClassDescriptor
*
* @return the XMLClassDescriptor
*/
public XMLClassDescriptor getClassDescriptor() {
return _classDesc;
}
/**
* Returns the associated XMLFieldDescriptor
*
* @return the associated XMLFieldDescriptor
*/
public XMLFieldDescriptor getFieldDescriptor() {
return _fieldDesc;
}
}
/**
* To set the SAX {@link ContentHandler} which is used as destination at marshalling.
* @param contentHandler the SAX {@link ContentHandler} to use as destination at marshalling
*/
public void setContentHandler(final ContentHandler contentHandler) {
_handler = contentHandler;
}
} //-- Marshaller