Extended Types has been edited by Andrus Adamchik (Sep 01, 2007).

(View changes)

Content:

JDBC specification defines a set of "standard" database column types (defined in java.sql.Types class) and a very specific mapping of these types to Java Object Types, such as java.lang.String, java.math.BigDecimal, etc. Sometimes there is a need to use a custom Java type not known to JDBC driver. CayenneModeler allows to configure an arbitrary Java class as an org.apache.cayenne.map.ObjAttribute type by simply entering a fully-qualified name such class in the type column of an ObjAttribute. However there is more to it than just that. Cayenne needs to know how to instantiate this type from a database "primitive" value, and conversly, how to transform an object of the custom type to a JDBC-compatible object.

Supporting Non-Standard Types

org.apache.cayenne.access.types.ExtendedType interface serves to integrate a custom attribute type to Cayenne. An implementation must provide ExtendedType.getClassName() method that returns a fully qualified Java class name for the supported custom type, and a number of methods that convert data between JDBC and custom type. Installing an ExtendedType currently has to be done in the code, some time during Cayenne startup (modeler support will be added in the future). The following code sample demonstrates this procedure:

// create custom ExtendedType instance
ExtendedType customType = new MyCustomType();

// Find DataNode
DataDomain domain = Configuration.getSharedConfiguration().getDomain();
// replace 'node_name' with the name of the DataNode you've entered in the Modeler.
DataNode node = domain.getNode("node_name");

// install ExtendedType
node.getAdapter().getExtendedTypes().registerType(customType);

DbAdapters and Extended Types

As shown in the example above, ExtendedTypes are stored by DbAdapter. In fact DbAdapters often install their own extended types to address incompatibilities, incompletness and differences between JDBC drivers in handling "standard" JDBC types. For instance some drivers support reading large character columns (CLOB) as java.sql.Clob, but some other - as "character stream", etc. Adapters provided with Cayenne override configureExtendedTypes() method to install their own types, possibly substituting Cayenne defaults. Custom DbAdapters can use the same technique.

Mapping Java Enums

Note that custom enums are supported by Cayenne natively. Cayenne stores an enum name in the database if the corresponding database column is a character column, and enum ordinal, if the column is numeric. So in a simple case no special ExtendedType is needed to map enums. Still often you may need one when you want to store a non-standard enum property in the database.

Take for example the following enum:

public enum MyEnum {

	E1(55), E2(97), E3(101);
	
	private int code;
	
	private MyEnum(int code) {
		this.code = code;
	}
	
	public int getCode() {
		return code;
	}
}

To store and retrieve string values "E1", "E2", "E3" no ExtendedType is needed. To store "code" value a custom ExtendedType is needed.

Extended types with ROP three tier

If you have a three tier setup (with Cayenne ROP) a final consideration is that your custom class must be serializable so that it can be passed between the client and the server. Cayenne uses a library called Hessian to perform the serialization of objects. Mostly it will just work with custom extended types, but there are some important requirements:

  • when Hessian deseralizes the stream back into an object it will construct that object by looking for a bean constructor with no parameters. If that isn't found, then the next shortest constructor is called with all parameters set to null. Your extended type class needs to support this. If your extended type subclasses BigDecimal for example, by default this will result in an exception (null is not allowed), so you'll need to add another constructor.
  • Hessian will then set the values for all fields in your custom extended type. These fields therefore need to be serializable in themselves unless they are set as volatile.
  • You can optionally define some additional helper functions within your extended type which Hessian will use.
    private Object writeReplace() throws ObjectStreamException {
    	// return some object which Hessian will serialize instead of your extended type
    }
    
    private Object readResolve() throws ObjectStreamException {
    	// return the final object in its deserialised form
    }

    Note that the functions are private - that is fine and encouraged since these functions should not normally be available to a subclass.

Instead of all the above you may be able to register an additional SerializerFactory to handle your extended type by using com.caucho.hessian.io.SerializerFactory.addFactory((AbstractSerializerFactory factory). This will give you complete control over the process.

Reply via email to