Marshalling objects

This section describes the various ways of marshalling JaxMe objects and how to configure the marshalling process. Note, that this section uses both methods and features, which are specified by JAXB and others, which are proprietary to JaxMe. Explicit remarks will tell you about the latter, so that you are still able to write 100% portable code.

Marshaller methods

The following methods are specified by JAXB. We'll present each method, followed by a small piece of example code.

Marshaller methods

public void marshal(Object, OutputStream) throws JAXBException

Marshals the object into the given java.io.OutputStream.

Example 2.1. Marshalling into an OutputStream

    public void writeToFile(
                            JAXBContext pContext, 
                            Object pObject, 
                            String pFileName)
                                throws  JAXBException, 
                                        IOException {
        
        java.io.FileOutputStream fos = new FileOutputStream(pFileName);
        
        Marshaller marshaller = pContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        marshaller.marshal(pObject, fos);

        fos.close();
    }
          	  

The important thing to note in our case is the use of an encoding. Depending on the encoding, the Marshaller will convert characters (16 bit entities) into bytes (8 bit entities). The encoding may also have influence on the marshallers decision, which characters to escape using &#ddd; or not.

public void marshal(Object, Writer) throws JAXBException

Marshals the object into the given java.io.Writer.

Example 2.2. Marshalling into a Writer

    public void writeToFile(
                            JAXBContext pContext, 
                            Object pObject, 
                            String pFileName)
                                throws 
                                        JAXBException, 
                                        IOException {
                                        
        java.io.FileWriter fw = new FileWriter(pFileName);
        
        Marshaller marshaller = pContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        marshaller.marshal(pObject, fw);

        fw.close();
    }
          	  

This example is almost equivalent to the previous example with an OutputStream as the target. In practice, the result may be slightly different anyways, for example, because the Marshaller may choose a different set of characters to escape.

Example 2.3. Marshalling into a String

    public String asString(
                            JAXBContext pContext, 
                            Object pObject)
                                throws 
                                    JAXBException {
                                    
        java.io.StringWriter sw = new StringWriter();
        
        Marshaller marshaller = pContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        marshaller.marshal(pObject, sw);
        
        return sw.toString();
    }
          	  

The example shows, how to convert a JaxMe object into a String. Note that the use of an encoding still applies, although there occurs no conversion from characters into bytes. (In the former example using the FileWriter there has been an implicit conversion, because any file is in fact a byte stream.) However, the Marshaller may still choose to escape characters or not.

public void marshal(Object, org.w3c.dom.Node) throws JAXBException

This method is used to convert a JaxMe object into a DOM tree. An instance of org.w3c.dom.Element will be created and appended to the given node.

Note

The node must be ready to accept an element as a child. Suitable nodes are, for example, documents, document fragments, or elements.
As an example, we'll demonstrate how to convert the JaxMe object into an instance of org.w3c.dom.Document.

Example 2.4. 

    public Document asDOMDocument(
                                    JAXBContext pContext, 
                                    Object pObject,
                                    javax.xml.parsers.DocumentBuilderFactory pFactory)
                                        throws 
                                            JAXBException, 
                                            javax.xml.parsers.ParserConfigurationException {
                                            
        org.w3c.dom.Document result = pFactory.newDocumentBuild().newDocument();
        
        Marshaller marshaller = pContext.createMarshaller();
        marshaller.marshal(pObject, result);
        
        return result;
    }
              

Note, that no encoding issues arise in this example, because the DOM tree is an abstract representation of the XML documents, much like JaxMe objects. However, the same issues are still to be applied, if you serialize the DOM tree into an OutputStream or Writer.

public void marshal(Object, org.xml.sax.ContentHandler) throws JAXBException

This method is by far the most powerfull of all the marshaller methods: A ContentHandler is in fact just a generic XML processor, implemented as an event handler. In the case of JaxMe all the other marshalling methods are just thin wrappers around this one: XML serialization is implemented by the XMLWriter, which is just an extension of the ContentHandler. As an example, we'll demonstrate how to transform a JaxMe object using an XSL stylesheet, writing the result into an OutputStream:

Example 2.5. Transformation of a JaxMe object using an XSL stylesheet

	public void transformToFile(JAXBContext pContext, 
                                Object pObject,
	                            TransformerFactory pFactory, 
                                String pStylesheetFileName,
	                            String pOutputFileName)
                                    throws 
                                        JAXBException, 
                                        TransformerConfigurationException {
                                        
          javax.xml.transform.Source stylesheetSource =
                        new javax.xml.transform.StreamSource(
                                    new java.io.File(pStylesheetFileName));
                                    
          javax.xml.transform.Result outputResult =
                        new javax.xml.transform.StreamResult(
                                    new java.io.File(pOutputFileName));
                                    
          javax.xml.transform.sax.TransformerHandler handler =
                    ((SAXTransformerFactory) pFactory).newTransformerHandler(stylesheetSource);
                    
          handler.setResult(outputResult);
          handler.getTransformer().setOutputProperty("output.encoding", "UTF-8");
          
          Marshaller marshaller = pContext.createMarshaller();
          marshaller.marshal(pObject, handler);
    }
		      

Note

The SAX ContentHandler receives an abstract representation of the JaxMe object. So there's no need to set the Marshaller's encoding. This is the same as in the DOM example.

However, the above example contains in fact two transformations: The first one is the stylesheet transformation, performed by the transformerHandler. But there is another, implicit, transformation, which the transformerHandler also performs, because we have choosen an instance of StreamResult as the transformers destination. In other words, its the Transformer who needs to know the desired encoding and not the Marshaller. That's why we set the stylesheet parameter output.encoding.

public void marshal(Object, javax.xml.transform.Result) throws JAXBException

This marshalling method is a generalization of the methods we've seen so far. An instance of Result is an abstraction of the marshalling destination. Concrete implementations are the StreamResult (writing to an OutputStream or Writer), the DOMResult (creating a DOM tree), and the SAXResult, which fires SAX events into a ContentHandler.

In theory, a Result can encapsulate arbitrary destinations. In practice one is restricted to the above standard cases, because the Result has no useful methods at all. In other words, a new implementation can only be used, if all users of the Result know how to deal with it. In our case the users include JaxMe.

The above limitations are best described, if we use the JaxMe implementation of the method as an example:

Example 2.6. JaxMe's implementation of the Result destination

    public void marshal(Object pObject, Result pResult) 
                            throws JAXBException {
                            
        if (pResult instanceof SAXResult) {
        
            ContentHandler ch = ((SAXResult) pResult).getHandler();
            if (ch == null) {
            
                throw new MarshalException("The SAXResult doesn't have its ContentHandler set.");
            }
            marshal(pObject, ch);
            
        } else if (pResult instanceof StreamResult) {
        
            StreamResult sr = (StreamResult) pResult;
            Writer w = sr.getWriter();
            if (w == null) {
            
                OutputStream s = sr.getOutputStream();
                if (s == null) {
                
                    throw new MarshalException(
                        "The StreamResult doesn't have its Writer or OutputStream set.");
                }
                marshal(pObject, s);
                
            } else {
            
                marshal(pObject, w);
            }
        } else if (pResult instanceof DOMResult) {
        
            Node node = ((DOMResult) pResult).getNode();
            if (node == null) {
            
                throw new MarshalException("The DOMResult doesn't have its Node set.");
            }
            marshal(pObject, node);
    
        } else {
        
            throw new MarshalException(
                "Unknown type of Result: " + pResult.getClass().getName() +
                ", only SAXResult, StreamResult and DOMResult are supported.");
        }
    }
              

In other words, the method is implemented as a thin wrapper around the others we've seen so far. (Funny enough, it would have been possible to go the other way and implement all others as wrappers around this one. However, that would require the presence of an XSLT implementation at runtime and most possibly quite inefficient.

Marshaller properties

In the previous section we've already seen how to configure the Marshaller's encoding by setting a property. In this section we'll present a complete list of all the properties supported by the JaxMe Marshaller.

JAXB standard Marshaller properties

The following properties are part by the JAXB specification. In other words: You are safe to use them with any JAXB compliant implementation, including JaxMe. Whenever possible, we recommend to use only this properties and no others.

JAXB standard Marshaller properties

jaxb.encoding, Marshaller.JAXB_ENCODING

As we have already seen, this property specifies the encoding used by the Marshaller, if applicable. The encoding is used to determine

  • when a character will be escaped using the notation &#ddd; and
  • if the method marshal(Object, OutputStream) is used, how the character is converted into bytes

For example, if we have the encoding ISO-8859-1 (also known as Latin-1), which is mainly used in the U.S. and western europe (Great Britain, France, Germany, ...) then the Marshaller knows, that no other characters that the character set should be limited to the Unicode points 0..255. In other words, all other characters will be escaped. The Latin-1 characters, however, will be passed through to the Writer, or OutputStream, because they are immediately mapped to the corresponding bytes.

jaxb.formatted.output, Marshaller.JAXB_FORMATTED_OUTPUT

By default, any Marshaller will create formatted output, when marshalling to an OutputStream or Writer. In other words, the created output will look roughly like the following:


<Person>
  <FullName>
    <FirstName>Jochen</FirstName>
    <SurName>Wiedmann</SurName>
  </FullName>
</Person>

	        
As opposed to unformatted output:

<Person><FullName><FirstName>Jochen</FirstName><SurName>Wiedmann</SurName></FullName></Person>

	        
The following example turns off the default handling and creates a String containing unformatted XML:

Example 2.7. Creating unformatted XML

    public String asUnformattedString(
                                JAXBContext pContext, 
                                Object pObject, 
                                String pFileName)
                                    throws 
                                        JAXBException {
                                        
        java.io.StringWriter sw = new StringWriter();
        
        Marshaller marshaller = pContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.FALSE);
        marshaller.marshal(pObject, sw);
        
        return sw.toString();
    }
                

JaxMe specific Marshallerproperties

Warning

The following properties must not be used for portable applications, because they are proprietary to JaxMe.

JaxMe specific Marshallerproperties

jaxme.datatype.converter, JMControllerImpl.JAXME_DATATYPE_CONVERTER

The javax.xml.bind.DatatypeConverterInterface is used by JAXB implementations to convert various data types into strings and vice versa. For example, there is a method parseInt(String), converting the given string into a primitive integer. Likewise, there is a reverse method printInt(int), converting the same integer back into a string. The various "parse" methods are used by the Unmarshaller and the "print" methods are used by the Marshaller.

The datatype converter is accessible for two reasons: First of all, you need one when writing custom parse and print methods (as specified by the jaxb:property/jaxb:conversion tags in the schema). In that case you can use a datatype converter by invoking the static methods of the class DatatypeConverter, which is internally using a single, global instance of DatatypeConverterInterface.

In the special case of JaxMe, however, you are by no means restricted to the global instance. In contrary, any JaxMe Marshaller or Unmarshaller holds an internal instance, by default the global instance from DatatypeConverter.

The advantage is, that you may customize how documents marshalled by JaxMe may look like. As an example, we'll show how to customize the look of the data type xs:datetime. The XML Schema specification requests, that timestamps are marshalled as yyyy-MM-ddThh:mm:ss, which only few people will like. Additionally the specification requires that timestamps are given in UTC time, not local time.

The specification does so for good reason: Timezone and Locale issues are eliminated. If your XML instances conform to the XML Schema format, it is most likely that you won't have interoperability problems with foreign software. Fact is, however, that not all of us are working in a multinational environment and prefer human readable formats and local time.

Example 2.8. Using a custom datatype converter

    // Create a subclass of the JaxMe datatype converter, overriding its parseDateTime() and
    // printDateTime() methods.
    public class MyDatatypeConverter extends org.apache.ws.jaxme.impl.DatatypeConverterImpl {

        public Calendar parseDateTime(String arg0) {
        
            DateFormat df = DateFormat.getDateTimeInstance();
            Calendar cal = Calendar.getInstance();
            try {
            
                cal.setTime(df.parse(arg0));
                
            } catch (ParseException e) {
            
                if (df instanceof SimpleDateFormat) {
                
                    SimpleDateFormat sdf = (SimpleDateFormat) df;
                    throw new IllegalArgumentException(
                        "Failed to parse dateTime " + arg0 +
                        " with pattern " + sdf.toPattern());
                } else {
            
                    throw new IllegalArgumentException("Failed to parse dateTime " + arg0);
                }
            }
            return cal;
        }
        
        public String printDateTime(Calendar arg0) {
        
            return DateFormat.getDateTimeInstance().format(arg0.getTime());
        }
    }


    // Convert a JaxMe object into a String, using the above converter
    public String asString(JAXBContext pContext, Object pObject)
                                throws JAXBException {
                                                
        java.io.StringWriter sw = new StringWriter();
        
        Marshaller marshaller = pContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        marshaller.setProperty(
                                JMControllerImpl.JAXME_DATATYPE_CONVERTER, 
                                new MyDatatypeConverter());
        marshaller.marshal(pObject, sw);
        
        return sw.toString();
    }
                

Warning

The above example may cause problems, because it depends on a suitable implementation of the local date format. For example, in some asiatic locales, the default format will contain local characters. The effect is, that you'll need to specify a proper encoding. UTF-8 is always the recommended choice.
jaxme.indentation.separator, JMMarshallerImpl.JAXME_INDENTATION_SEPARATOR

This property is used in conjunction with formatted output. If formatted output is turned on, it contains the string which is used as a line terminator. The property is ignored, if formatted output is turned off or marshalling to a DOM tree or SAX ContentHander occurs.

The default property value is Line Feed (ASCII 10, or \n), which is the standard on Linux or Unix. On Windows the standard is Carriage Return, followed by a Line Feed (ASCII 13 and 10, or \r\n). Rumours are, they are even using a single Carriage Return on some Apple Macintosh boxes.

If you want to see the problem live, just take a file with Unix line terminators and open it in the Windows application Notepad. No matter, which version of Windows you choose, it remains the same after 20 years of development: You'll see a text file containing a single line and where you'd expect the lines being splitted, the editor will show you black boxes only. But rumbling doesn't help, we've got to live with it.

The following example shows how to turn on Windows line terminators:

Example 2.9. Turning on Windows line terminators

    public String asUnformattedString(
                                        JAXBContext pContext, 
                                        Object pObject, 
                                        String pFileName)
                                            throws JAXBException {
                                            
        java.io.StringWriter sw = new StringWriter();
        
        Marshaller marshaller = pContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        marshaller.setProperty(JMMarshallerImpl.JAXME_INDENTATION_STRING, "\r\n");
        marshaller.marshal(pObject, sw);
        
        return sw.toString();
    }
                

jaxme.indentation.string, JMMarshallerImpl.JAXME_INDENTATION_STRING

This property is used in conjunction with formatted output. If formatted output is turned on, it contains the string which is used to indent one element: By default " " (two blanks). The property is ignored, if formatted output is turned off or when marshalling to a DOM tree or SAX ContentHandler occurs.

To demonstrate the effect, we'll show two example documents. First an instance with default indentation:


<Person>
  <FullName>
    <FirstName>Jochen</FirstName>
    <SurName>Wiedmann</SurName>
  </FullName>
</Person>

                
If we set the indentation string to " " (four blanks), we'll get

<Person>
    <FullName>
        <FirstName>Jochen</FirstName>
        <SurName>Wiedmann</SurName>
    </FullName>
</Person>

                

jaxme.private., JMControllerImpl.JAXME_PRIVATE_

When writing custom code for formatting or parsing element or attribute values, you will most probably find at any time, that you wish to access data, which isn't available by default. In most cases you have the data, when invoking the Marshaller but not inside the formatter or parser. A typical example might be, that you want to localize the resulting XML document by using a resource bundle.

The JaxMe Marshaller allows to set arbitrary, application specific properties, but only if they are prefixed with jaxme.private.. The idea is that an unknown property name is most probably just a typo or a similar problem and it is better to throw an exception to let the programmer know that there's something wrong. However, if the property name is prefixed with jaxme.private., the JaxMe user advises, that "this unknown property is fine".

Warning

Custom property values are arbitrary. However, you should not attempt to distinguish the between the cases null and missing, because the JAXB specification has nothing like a collections method contains(Object).
jaxme.xml.declaration, JMMarshallerImpl.JAXME_XML_DECLARATION

When writing to an OutputStream or Writer, it is likely that you want your resulting XML document to have an XML declaration like

    <?xml version="1.0" encoding="UTF-8"?>
                
By default JaxMe does not create such a declaration, because that would make it difficult to embed the resulting XML into a larger document.

An XML declaration is particularly important, if you use another encoding than UTF-8. In that case you should consider a declaration as mandatory.

The property has no effect, when marshalling to DOM trees, or SAX handlers.

Example 2.10. Enabling an XML declaration

    // Convert a JaxMe object into a String, using the above converter
    public String asString(JAXBContext pContext, Object pObject)
                                                throws JAXBException {
                                                
        java.io.StringWriter sw = new StringWriter();
        
        Marshaller marshaller = pContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        marshaller.setProperty(JMMarshallerImpl.JAXME_XML_DECLARATION, Boolean.TRUE);
        marshaller.marshal(pObject, sw);
        
        return sw.toString();
    }
                

jaxme.xml.writer, JMMarshallerImpl.JAXME_XML_WRITER

If the above properties still don't satisfy your needs, this property is your friend. It allows to take complete control on the created XML document, when marshalling to an OutputStream or Writer. (It takes no effect otherwise.)

The property value is an instance of java.lang.Class implementing org.apache.ws.jaxme.XMLWriter, which is in turn extending org.xml.sax.ContentHandler. That means, that you can do arbitrary things, including use of an XSLT stylesheet and stuff like that.

JaxMe comes with three ready-to-use implementations:

org.apache.ws.jaxme.impl.XMLWriterImpl

This is the default implementation, suitable for any encoding and Java platform. It is very simple: It is in fact using the US-ASCII encoding, escaping all Unicode points above 126.

org.apache.ws.jaxme.impl.CharSetXMLWriter

This implementation is used, whenever you are running Java 1.4 or newer. It is internally using the class java.nio.charset.CharsetEncoder to determine the Unicode points being escaped.

org.apache.ws.jaxme.impl.PassThroughXMLWriter

This implementation does no escaping at all.

As an example, we'll write and use and XMLWriter which is escaping the german Umlauts, but no other characters.

Example 2.11. Implementation of an XMLWriter escaping german Umlauts

    public class UmlautXMLWriter extends org.apache.ws.jaxme.impl.XMLWriterImpl {
      protected boolean canEncode(char c) {
        switch (c) {
          case 228:
          case 246:
          case 252:
          case 196:
          case 214:
          case 220:
          case 223:
            return false;
          default:
            return true;
        }
      }
    }

    // Convert a JaxMe object into a String, using the above XMLWriter
    public String asString(JAXBContext pContext, Object pObject)
                                                        throws JAXBException {
                                                        
        java.io.StringWriter sw = new StringWriter();
        
        Marshaller marshaller = pContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        marshaller.setProperty(JMMarshallerImpl.JAXME_XML_WRITER, UmlautXMLWriter.class);
        marshaller.marshal(pObject, sw);
        
        return sw.toString();
    }