Search code examples
javaserializationjbossmarshallingdom4j

Can't serialize org.dom4j.Document using JBoss Marshalling API


I'm hitting into an issue when trying to serialize a Java object with org.dom4j.Document as an attribute using JBoss Marshalling API.

I'm using JDK 1.7

enter image description here

Same issue with dom4j 1.5.2.

Java Object :

package test.t4;
import org.dom4j.Document;
public class Movie implements java.io.Serializable {

    private static final long serialVersionUID = 1L;

    private String title;
    private String director;
    private transient int code;
    private int year;
    private Document docXml;

    public Movie() {
        super();
    }

    public Movie(String title, String director, int code, int year) {
        super();
        this.title = title;
        this.director = director;
        this.code = code;
        this.year = year;
    }

    //Getters and Setters

    @Override
    public String toString() {
        return "Movie [title=" + title + ", director=" + director + ", year="
                + year + ", docXml=" + docXml + "]";
    }

}

Main Class:

package test.t4;

import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.jboss.marshalling.Marshaller;
import org.jboss.marshalling.MarshallerFactory;
import org.jboss.marshalling.Marshalling;
import org.jboss.marshalling.MarshallingConfiguration;
import org.jboss.marshalling.river.RiverMarshallerFactory;

public final class WriteExample {

    final static String file = "D:/TEST/serialization/jboss40.ser";

    public static void main(String[] args) {

        Document docXml = null;
        Movie mov0 = new Movie("The Hobbit: An Unexpected Journey",
                "Peter Jackson", 123456780, 2012);
        docXml = createDocument();
        mov0.setDocXml(docXml);

        Map<String, Movie> maps = new HashMap<String, Movie>();
        maps.put("Hobbit", mov0);

        System.out.println("maps : " + maps);

        goMarshall(maps);
      //serialize(maps);//When using the native Serialization Java API, the serialization works.
    }

public static void serialize(Map<String, Movie> maps) {
    try {
        FileOutputStream fileOut = new FileOutputStream(fileJDKSerialize);
        ObjectOutputStream out = new ObjectOutputStream(fileOut);
        out.writeObject(maps);
        out.close();
        fileOut.close();
        System.out
                .printf("Serialized data is saved in " + fileJDKSerialize);
    } catch (IOException i) {
        i.printStackTrace();
    }
}

    public static Document createDocument() {
        Document document = DocumentHelper.createDocument();
        Element starring = document.addElement("Starring");

        Element actor1 = starring.addElement("actor")
                .addAttribute("name", "Martin Freeman")
                .addAttribute("character", "Bilbo").addText("Good morning.");
        Element actor2 = starring
                .addElement("actor")
                .addAttribute("name", "Ian McKellen")
                .addAttribute("character", "Gandalf")
                .addText(
                        "What do you mean? Do you mean to wish me a good morning or do you mean that it is a good morning whether I want it or not? Or perhaps you mean to say that you feel good on this particular morning. Or are you simply stating that this is a morning to be good on? ");

        return document;
    }

public static void goMarshall(Object obj) {
    // Get the factory for the "river" marshalling protocol
    final MarshallerFactory marshallerFactory = new RiverMarshallerFactory();// Marshalling.getProvidedMarshallerFactory("river");

    // Create a configuration
    final MarshallingConfiguration configuration = new MarshallingConfiguration();
    // Use version 3
    configuration.setVersion(3);
    FileOutputStream os = null;
    try {

        final Marshaller marshaller = marshallerFactory
                .createMarshaller(configuration);
        os = new FileOutputStream(file);
        marshaller.start(Marshalling.createByteOutput(os));
        marshaller.writeObject(obj);
        marshaller.finish();
        os.close();

    } catch (IOException e) {
        System.err.print("Marshalling failed: ");
        e.printStackTrace();
    }
}
}

Error StackTrace:

maps : {Hobbit=Movie [title=The Hobbit: An Unexpected Journey, director=Peter Jackson, year=2012, docXml=org.dom4j.tree.DefaultDocument@570c16b7 [Document: name null]]} Marshalling failed: java.io.NotActiveException: writeFields() may only be called when the fields have not yet been written at org.jboss.marshalling.river.RiverObjectOutputStream.defaultWriteObject(RiverObjectOutputStream.java:162) at org.dom4j.QName.writeObject(QName.java:239) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.jboss.marshalling.reflect.SerializableClass.callWriteObject(SerializableClass.java:271) at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:976) at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:854) at org.jboss.marshalling.river.RiverMarshaller.doWriteFields(RiverMarshaller.java:1032) at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:988) at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:967) at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:854) at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:569) at org.jboss.marshalling.river.RiverMarshaller.doWriteFields(RiverMarshaller.java:1032) at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:988) at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:854) at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:569) at org.jboss.marshalling.river.RiverMarshaller.doWriteFields(RiverMarshaller.java:1032) at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:988) at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:854) at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:569) at org.jboss.marshalling.river.RiverMarshaller.doWriteFields(RiverMarshaller.java:1032) at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:988) at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:854) at org.jboss.marshalling.river.RiverMarshaller.doWriteFields(RiverMarshaller.java:1032) at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:988) at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:854) at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.java:65) at org.jboss.marshalling.river.BlockMarshaller.writeObject(BlockMarshaller.java:56) at org.jboss.marshalling.MarshallerObjectOutputStream.writeObjectOverride(MarshallerObjectOutputStream.java:50) at org.jboss.marshalling.river.RiverObjectOutputStream.writeObjectOverride(RiverObjectOutputStream.java:179) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:343) at java.util.HashMap.writeObject(HashMap.java:1129) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.jboss.marshalling.reflect.SerializableClass.callWriteObject(SerializableClass.java:271) at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:976) at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:854) at org.jboss.marshalling.AbstractObjectOutput.writeObject(AbstractObjectOutput.java:58) at org.jboss.marshalling.AbstractMarshaller.writeObject(AbstractMarshaller.java:111) at test.t4.WriteExample.goMarshall(WriteExample.java:119) at test.t4.WriteExample.main(WriteExample.java:43) Caused by: an exception which occurred: in field qname in field attributes in field content in field content in field docXml in object java.util.HashMap@9ffb6306

The issue is detected precisely in the object org.dom4j.QName.


Solution

  • The reason this happens is explained in the Java Serialization specification:

    The class's writeObject method, if implemented, is responsible for saving the state of the class. Either ObjectOutputStream's defaultWriteObject or writeFields method must be called once (and only once) before writing any optional data that will be needed by the corresponding readObject method to restore the state of the object; even if no optional data is written, defaultWriteObject or writeFields must still be invoked once. If defaultWriteObject or writeFields is not invoked once prior to the writing of optional data (if any), then the behavior of instance deserialization is undefined in cases where the ObjectInputStream cannot resolve the class which defined the writeObject method in question.

    Note the sentence "...defaultWriteObject or writeFields method must be called once (and only once) before writing any optional data..." (emphasis added).

    The QName class is calling writeFields after writing optional data, hence it is in violation of the spec. That said, I've been thinking about trying to find a way to be more lenient about this restriction, but for now there isn't a simple fix, short of adding in a class substitution to both ends that swaps out that class for one that is properly serializable in a compliant manner.

    Thanks to David Lloyd (https://issues.jboss.org/browse/JBMAR-174)

    UPDATE:

    Question : Thank you David, i understand now. But i still have a question, when using the native Java Serialization API the issue is not encountered and the Serialization works (The spec. violation in dom4j doesn't seem to bother the questioned Java API). Is there any difference form JBoss Marshalling ? (I updated the example above).

    Answer : Yes because the Oracle serialization implementation is more lenient than the specification.

    The Java Serialization specification for more detail : http://docs.oracle.com/javase/7/docs/platform/serialization/spec/output.html#861