![]() | ![]() |
![]() |
| JPOX | Version 1.0 | Version 1.1 | Version 1.2 | JDO |
| 1.1 | Preparation | Runtime | Tutorials and Examples | Developer |
![]() When persisting a class, clearly a persistence solution can only support a finite number of Java types to persist. It cannot know how to persist every possible type creatable. The JDO specifications define a list of types that are required to be supported. JPOX supports all types required by the JDO specifications. JPOX also allows users to define their own type mappings. This page defines how to utilise this facility, though please note that this facility is currently experimental and is present only in JPOX from version 1.1.0-beta-4. A type mapping defines the way to convert between an object of the Java type and its datastore representation (namely a column or columns in a datastore table). There are 2 types of Java types that can be mapped. These are mutable (something that can be updated, such as java.util.Date) and immutable (something that is fixed from the point of construction, such as java.awt.Color).
To map any Java type to a datastore column you need a mapping class. The simplest way to describe how to define your own mapping for an immutable type is to give an exmaple. Lets assume we have our own class IPAddress that represents an address such as 192.168.1.1 and we want to map this to a single VARCHAR column in the datastore
public class IPAddress
{
String ipAddress;
public IPAddress(String ipAddr)
{
ipAddress = ipAddr;
}
public String toString()
{
return ipAddress;
}
}We start by defining a JavaTypeMapping for this type.
import org.jpox.store.mapping.SingleFieldMapping;
import org.jpox.store.mapping.SimpleDatastoreRepresentation;
public class IPAddressMapping extends SingleFieldMapping implements SimpleDatastoreRepresentation
{
private static IPAddress mappingSampleValue = new IPAddress("192.168.1.1");
/**
* Constructor.
* @param dba Datastore Adapter
* @param type Type of the field
*/
public IPAddressMapping(DatastoreAdapter dba, String type)
{
super(dba, type);
}
/**
* Creates a new Mapping object.
* @param dba Database Adapter
* @param fmd AbstractPropertyMetaData for the field
* @param datastoreContainer Table for persisting this field
* @param clr The ClassLoaderResolver
*/
public IPAddressMapping(DatastoreAdapter dba, AbstractPropertyMetaData fmd,
DatastoreContainerObject datastoreContainer, ClassLoaderResolver clr)
{
super(dba, fmd, datastoreContainer);
}
public Object getSampleValue()
{
return mappingSampleValue;
}
public ScalarExpression newLiteral(QueryExpression qs, Object value)
{
ScalarExpression expr = new StringLiteral(qs, this, ((IPAddress)value).toString());
return expr;
}
public ScalarExpression newScalarExpression(QueryExpression qs, LogicSetExpression te)
{
ScalarExpression expr = new StringExpression(qs, this, te);
return expr;
}
public Class getJavaType()
{
return IPAddress.class;
}
}Now we want to map this in the datastore to a VARCHAR type. So we define our RDBMSMapping to define how we map the java type to/from the RDBMS type.
import org.jpox.store.rdbms.mapping.ColumnMapping;
public class IPAddressVarcharRDBMSMapping extends ColumnMapping
{
/**
* Constructor.
* @param storeMgr Store Manager
* @param mapping Java type mapping
*/
protected IPAddressVarcharRDBMSMapping(StoreManager storeMgr, JavaTypeMapping mapping)
{
super(storeMgr, mapping);
}
/**
* Constructor.
* @param mapping Java type mapping
* @param storeMgr Store Manager
* @param field Field to be mapped
*/
public IPAddressVarcharRDBMSMapping(JavaTypeMapping mapping, StoreManager storeMgr, DatastoreField field)
{
super(storeMgr, mapping);
column = (Column) field;
initialize();
}
/**
* Method to initialise the column mapping.
* Provides default length specifications for the CHAR column to fit the data being stored.
*/
protected void initialize()
{
if (column != null)
{
int maxlength = getTypeInfo().precision;
column.checkString();
if (column.getColumnMetaData().getLength().intValue() <= 0 ||
column.getColumnMetaData().getLength().intValue() > maxlength)
{
throw new JDOUserException("String max length of " + column.getColumnMetaData().getLength() +
" is outside the acceptable range [0, " + maxlength + "] for column \"" + column.getIdentifier() + "\"");
}
}
initTypeInfo();
}
/**
* Accessor for the JDBC type being stored here.
* @return JDBC Types.VARCHAR
*/
public TypeInfo getTypeInfo()
{
return getDatabaseAdapter().getTypeInfo(Types.VARCHAR);
}
/**
* Method to set an object at the specified position in the JDBC PreparedStatement.
* @param ps The PreparedStatement
* @param param Parameter position
* @param value The value to set
*/
public void setObject(Object ps, int param, Object value)
{
try
{
if (value == null)
{
((PreparedStatement) ps).setNull(param, getTypeInfo().dataType);
}
else
{
if (value instanceof IPAddress)
{
// Use toString() to get the value to be stored from our IPAddress
((PreparedStatement) ps).setString(param, ((IPAddress) value).toString());
}
}
}
catch (SQLException e)
{
throw new JDODataStoreException(LOCALISER.msg("RDBMS.Mapping.UnableToSetParam","Object","
" + value, column, e.getMessage()), e);
}
}
/**
* Method to extract an object from the ResultSet at the specified position
* @param rs The Result Set
* @param param The parameter position
* @return the object
*/
public Object getObject(Object rs, int param)
{
Object value;
try
{
String s = ((ResultSet) rs).getString(param);
if (s == null)
{
value = null;
}
else
{
if (getJavaTypeMapping().getJavaType().getName().equals(IPAddress.class.getName()))
{
// Construct the IPAddress using the String constructor
value = new IPAddress(s.trim());
}
}
}
catch (SQLException e)
{
throw new JDODataStoreException(LOCALISER.msg("RDBMS.Mapping.UnableToGetParam","Object",
"" + param, column, e.getMessage()), e);
}
return value;
}
}The only thing we need to do is enable use of this Java type when running JPOX. To do this we create a mapping definition file to contain our mappings.
<?xml version="1.0"?>
<plugin>
<extension point="org.jpox.store.datastoremapping">
<mapping java-type="mydomain.IPAddress" rdbms-mapping-class="mydomain.IPAddressVarcharRDBMSMapping"
jdbc-type="VARCHAR" sql-type="VARCHAR" default="true"/>
</extension>
<extension point="org.jpox.store.mapping">
<mapping java-type="mydomain.IPAddress" mapping-class="mydomain.IPAddressMapping"/>
</extension>
</plugin>When using the JPOX Enhancer, SchemaTool or Core, JPOX automatically searches for the mapping definition at /META-INF/plugins/plugin.xml and /plugin.xml files in the classpath. This is downloadable as a JPOX sample from the Download Page
To map any Java type to multiple datastore columns you need a mapping class. The simplest way to describe how to define your own mapping for an immutable type to multiple datastore columns is to give an example. Here we'll use the examle of the Java AWT class Color. This has 3 colour components (red, green, and blue) as well as an alpha component. So here we want to map the Java type java.awt.Color to 4 datastore columns - one for each of the red, green, blue, and alpha components of the colour. To do this we define a mapping class extending the JPOX class org.jpox.store.mapping.SingleFieldMultiMapping
package org.mydomain;
import org.jpox.store.mapping.JavaTypeMapping;
import org.jpox.metadata.FieldMetaData;
import org.jpox.store.DatastoreClass;
import org.jpox.store.DatastoreField;
import org.jpox.store.query.QueryStatement;
import org.jpox.store.expression.TableExpression;
import org.jpox.PersistenceManager;
public class MyColorMapping extends SingleFieldMultiMapping
{
public MyColorMapping(DatastoreAdapter dba, Class type)
{
super(dba, type);
}
public MyColorMapping(DatastoreAdapter dba, FieldMetaData fmd, DatastoreClass datastoreClass)
{
super(dba, fmd, datastoreClass);
addDatastoreField(int.class.getName()); // Red
addDatastoreField(int.class.getName()); // Green
addDatastoreField(int.class.getName()); // Blue
addDatastoreField(int.class.getName()); // Alpha
}
/**
* Return the Java type being represented.
*/
public Class getJavaType()
{
return Color.class;
}
/**
* Return a typical value for the Java type.
*/
public Object getSampleValue()
{
return java.awt.Color.RED;
}
public ScalarExpression newLiteral(QueryStatement qs, Object value)
{
return null;
}
public ScalarExpression newScalarExpression(QueryStatement qs, TableExpression te)
{
return null;
}
/**
* Method that sets the values to be put in the datastore columns from the Java object.
*/
public void setObject(PersistenceManager pm, Object preparedStatement, int[] exprIndex, Object value)
{
Color color = (Color) value;
getDataStoreMapping(0).setInt(preparedStatement, exprIndex[0], color.getRed());
getDataStoreMapping(1).setInt(preparedStatement, exprIndex[1], color.getGreen());
getDataStoreMapping(2).setInt(preparedStatement, exprIndex[2], color.getBlue());
getDataStoreMapping(3).setInt(preparedStatement, exprIndex[3], color.getAlpha());
}
/**
* Method to extract the values out of a JDBC ResultSet and return a Java object.
*/
public Object getObject(PersistenceManager pm, Object resultSet, int[] exprIndex)
{
int red = getDataStoreMapping(0).getInt(resultSet, exprIndex[0]);
int green = getDataStoreMapping(1).getInt(resultSet, exprIndex[1]);
int blue = getDataStoreMapping(2).getInt(resultSet, exprIndex[2]);
int alpha = getDataStoreMapping(3).getInt(resultSet, exprIndex[3]);
return new Color(red,green,blue,alpha);
}
}In the constructor we've created 4 columns - one for each of the red, green, blue, alpha components of the colour. The argument passed in when constructing these columns is the Java type name of the column data being stored. The other 2 methods of relevance are the setObject() and getObject(). These have the task of mapping between the Color object and its datastore representation (the 4 columns). That's all there is to it. The above example does not allow the Java type to be used in JDOQL queries. This would involve writing the methods newLiteral(), newScalarExpression() and this detail will be added in a future version of this guide. The only thing we need to do is enable use of this Java type when running JPOX. To do this we create a mapping definition file to contain our mappings.
<?xml version="1.0"?>
<plugin>
<extension point="org.jpox.store.mapping">
<mapping java-type="java.awt.Color" mapping-class="org.mydomain.MyColorMapping"/>
</extension>
</plugin>When using the JPOX Enhancer, SchemaTool or Core, JPOX automatically searches for the mapping definition at /META-INF/plugins/plugin.xml and /plugin.xml files in the classpath. Obviously, since JPOX already supports java.awt.Color there is no need to add this particular mapping to JPOX yourself, but this demonstrates the way you should do it for any type you wish to add. If your Java type that you want to map maps direct to a single column then you would instead extend org.jpox.store.mapping.SingleFieldMapping and wouldn't need to add the columns yourself. Look at JPOX CVS for many examples of doing it this way.
In the example above for an immutable type we only had to define a class for the mapping. For the mutable case we have to do more work. With a mutable type we need to define a mapping class (in the same style as what we did above), but we also have to provide a proxy, or wrapper class to detect the updates to the various fields in the Java type. This allows us to pass these changes through to the datastore. We can easily write a mapping class just like we did above for a mutable case. The process is the same. Let's concentrate here on how to write this wrapper class. The first thing is that we need to extend our Java type that we are adding support for. This is because when the users code encounters a class of this type it will treat it just like an object of the basic Java type. Let's take the example of a java.util.Date. This has a few (deprecated) methods for changing the date. We want to support persisting java.util.Date so we need to write a wrapper. So we extend "java.util.Date"
package org.mydomain;
import java.io.ObjectStreamException;
import javax.jdo.JDOHelper;
import javax.jdo.spi.PersistenceCapable;
import org.jpox.StateManager;
public class MyDateWrapper extends java.util.Date implements SCO
{
private transient PersistenceCapable owner;
private transient String fieldName;
public MyDateWrapper(StateManager ownerSM, String fieldName)
{
super();
this.owner = ownerSM.getObject();
this.fieldName = fieldName;
}
/** Wrapper for the setTime() method. Mark the object as "dirty" */
public void setTime(long time)
{
super.setTime(time);
makeDirty();
}
/** Wrapper for the setYear() deprecated method. Mark the object as "dirty" */
public void setYear(int year)
{
super.setYear(year);
makeDirty();
}
/** Wrapper for the setMonth() deprecated method. Mark the object as "dirty" */
public void setMonth(int month)
{
super.setMonth(month);
makeDirty();
}
/** Wrapper for the setDates() deprecated method. Mark the object as "dirty" */
public void setDate(int date)
{
super.setDate(date);
makeDirty();
}
/** Wrapper for the setHours() deprecated method. Mark the object as "dirty" */
public void setHours(int hours)
{
super.setHours(hours);
makeDirty();
}
/** Wrapper for the setMinutes() deprecated method. Mark the object as "dirty" */
public void setMinutes(int minutes)
{
super.setMinutes(minutes);
makeDirty();
}
/** Wrapper for the setSeconds() deprecated method. Mark the object as "dirty" */
public void setSeconds(int seconds)
{
super.setSeconds(seconds);
makeDirty();
}
public Object clone()
{
Object obj = super.clone();
((Date)obj).unsetOwner();
return obj;
}
public void setValueFrom(Object o)
{
super.setTime(((java.util.Date)o).getTime());
}
public void unsetOwner()
{
owner = null;
fieldName = null;
}
public Object getOwner()
{
return owner;
}
public String getFieldName()
{
return this.fieldName;
}
public void makeDirty()
{
if (owner != null)
{
JDOHelper.makeDirty(owner, fieldName);
}
}
public Object detachCopy()
{
return new java.util.Date(getTime());
}
/**
* Method to attached the passed value.
* @param value The new value
* @param makeTransactional Whether to make transactional
*/
public void attachCopy(Object value, boolean makeTransactional)
{
long oldValue = getTime();
setValueFrom(value);
// Check if the field has changed, and set the owner field as dirty if necessary
long newValue = ((java.util.Date)value).getTime();
if (oldValue != newValue)
{
makeDirty();
}
}
/**
* Handling for serialising our object.
*/
protected Object writeReplace() throws ObjectStreamException
{
return new java.util.Date(this.getTime());
}
}The next thing we need to do is enable use of this Java type when running JPOX. To do this we create a plugin file to contain our mapping definitions. When using the JPOX Enhancer, SchemaTool or Core, JPOX automatically searches for the mapping definition at /META-INF/plugins/plugin.xml and /plugin.xml files in the classpath.
<?xml version="1.0"?>
<plugin>
<extension point="org.jpox.store.mapping">
<mapping java-type="java.util.Date" mapping-class="org.mydomain.MyDateMapping" sco-wrapper="org.mydomain.MyDateWrapper"/>
</extension>
<plugin> |