3. Implementing serialization code
The easiest way to implement POF serialization is to make your objects implement the PortableObject interface.
public interface PortableObject extends Serializable {
void readExternal(PofReader pofReader)
throws IOException;
void writeExternal(PofWriter pofWriter)
throws IOException;
}
The methods defined by the PortableObject
interface return no value and accept a single argument that allows us to
read the indexed attributes from the POF stream, in the case of readExternal, or to write attributes into the POF stream in writeExternal. To learn how to implement these methods, let's look at the implementation of a POF-enabled Customer class:
public class Customer
implements Entity<Long>, PortableObject {
// data members
private final Long m_id;
private String m_name;
private String m_email;
private Address m_address;
private Collection<Long> m_accountIds;
public Customer() {
}
public void readExternal(PofReader pofReader)
throws IOException {
m_id = pofReader.readLong(0);
m_name = pofReader.readString(1);
m_email = pofReader.readString(2);
m_address = (Address) pofReader.readObject(3);
m_accountIds = pofReader.readCollection(4, new ArrayList<Long>());
}
public void writeExternal(PofWriter pofWriter)
throws IOException {
pofWriter.writeLong(0, m_d);
pofWriter.writeString(1, m_name);
pofWriter.writeString(2, m_email);
pofWriter.writeObject(3, m_address);
pofWriter.writeCollection(4, m_accountIds);
}
}
As you can see, while it probably isn't the most
exciting code to write, serialization code is fairly straightforward and
simply uses the appropriate PofReader and PofWriter methods to read and write attribute values.
Once you implement the PortableObject interface, all you need to do is register your user type within the POF context by adding a user-type element for it into the POF configuration file, as we've done in the previous section. You can omit the serializer element, and PortableObjectSerializer will be used by default.
The second way to implement serialization logic is to create a separate serializer class that implements the PofSerializer interface:
public interface PofSerializer {
void serialize(PofWriter pofWriter, Object o)
throws IOException;
Object deserialize(PofReader pofReader)
throws IOException;
}
As you can see, the methods that need to be implemented are very similar to the ones defined by the PortableObject interface, so the logic within them will also look familiar:
public class CustomerSerializer implements PofSerializer {
public void serialize(PofWriter writer, Object obj)
throws IOException {
Customer c = (Customer) obj;
writer.writeLong(0, c.getId());
writer.writeString(1, c.getName());
writer.writeString (2, c.getEmail());
writer.writeObject(3, c.getAddress());
writer.writeCollection(4, c.getAccountIds());
writer.writeRemainder(null);
}
public Object deserialize(PofReader reader)
throws IOException {
Long id = reader.readLong(0);
String name = reader.readString(1);
String email = reader.readString (2);
Address address = (Address) reader.readObject(3);
Collection<Long> accountIds =
reader.readCollection(4, new ArrayList<Long>());
pofReader.readRemainder();
return new Customer(id, name, email, address, accountIds);
}
}
Apart from the fact that we now have to use getters
to retrieve attribute values, there are only two things that are
different: calls to writeReminder and readReminder
methods, and the Customer creation in the deserialization method.
The read/writeReminder methods are part of
the schema evolution support, which we will discuss in more detail
shortly. For now, all you need to know is that you have to call them in
order to properly terminate reading or writing of a user type instance
from or into a POF stream. The PortableObjectSerializer does that for us, so we didn't have to worry about it when we implemented serialization within PortableObject methods, but you do need to do it in your custom implementations of PofSerializer.
The way the Customer instance is created within the deserialize
method points out one of the major differences between the two possible
serialization implementations: if you write a custom serializer you
have complete control over the object creation. You can use a
parameterized constructor, as in the previous example, or a factory that
encapsulates possibly complex creational logic.
On the other hand, in order for the PortableObjectSerializer
to work, your class needs to provide a public default constructor.
While in many cases this isn't a problem as there are other application
components that require default constructors, such as ORM tools, in some
situations this might be an issue and you might want to implement
external serializers in order to have complete control over the
instantiations of your objects. This brings us to our next topic: How to
decide on which option to use.