![]() | ![]() |
![]() |
| Project | Ver 1.1 | Ver 1.2 | JDO | JPA | Guides | Tools |
| 1.1 | Preparation | O/R Mapping | Runtime | Extensions | 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. 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). The examples in this guide relate to current JPOX CVS. Please consult the JPOX source code if you are using an earlier version since things have changed recently for user type mapping
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.ObjectAsStringMapping;
public class IPAddressMapping extends ObjectAsStringMapping
{
private static IPAddress mappingSampleValue = new IPAddress("192.168.1.1");
public Object getSampleValue(ClassLoaderResolver clr)
{
return mappingSampleValue;
}
/**
* Method to return the Java type being represented
* @return The Java type we represent
*/
public Class getJavaType()
{
return IPAddress.class;
}
/**
* Method to return the default length of this type in the datastore.
* An IP address can be maximum of 15 characters ("WWW.XXX.YYY.ZZZ")
* @return The default length
*/
public int getDefaultLength(int index)
{
return 15;
}
/**
* Method to set the datastore string value based on the object value.
* @param object The object
* @return The string value to pass to the datastore
*/
protected String objectToString(Object object)
{
String ipaddr;
if (object instanceof IPAddress)
{
ipaddr = ((IPAddress)object).toString();
}
else
{
ipaddr = (String)object;
}
return ipaddr;
}
/**
* Method to extract the objects value from the datastore string value.
* @param datastoreValue Value obtained from the datastore
* @return The value of this object (derived from the datastore string value)
*/
protected Object stringToObject(String datastoreValue)
{
return new IPAddress(datastoreValue.trim());
}
}We have extended the JPOX convenience class ObjectAsStringMapping and this provides the majority of the mapping for us (including JDOQL capabilities). We have defined a default length of 15 so that when an IP Address is to be mapped, if the user doesnt specify a length in the metadata it will be assigned a column of length 15. Finally we define the methods of converting the IPAddress into a String and back again. The only remaining 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="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 mapping definition is valid for JPOX from version 1.1.2 onwards. The most common user-type will map to a String and so can just take this as a template. If the user-type maps to a Long then they can use ObjectAsLongMapping - the other convenience mapping available. 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.AbstractPropertyMetaData;
import org.jpox.store.DatastoreContainerObject;
import org.jpox.store.query.QueryStatement;
import org.jpox.store.expression.TableExpression;
import org.jpox.PersistenceManager;
public class ColorMapping extends SingleFieldMultiMapping
{
/**
* Initialize this JavaTypeMapping with the given DatastoreAdapter for
* the given FieldMetaData.
*
* @param dba The Datastore Adapter that this Mapping should use.
* @param fmd FieldMetaData for the field to be mapped (if any)
* @param container The datastore container storing this mapping (if any)
* @param clr the ClassLoaderResolver
*/
public void initialize(DatastoreAdapter dba, AbstractPropertyMetaData fmd, DatastoreContainerObject container, ClassLoaderResolver clr)
{
super.initialize(dba, fmd, container, clr);
addDatastoreField(ClassNameConstants.INT); // Red
addDatastoreField(ClassNameConstants.INT); // Green
addDatastoreField(ClassNameConstants.INT); // Blue
addDatastoreField(ClassNameConstants.INT); // Alpha
}
/* (non-Javadoc)
* @see org.jpox.store.mapping.JavaTypeMapping#getJavaType()
*/
public Class getJavaType()
{
return Color.class;
}
/* (non-Javadoc)
* @see org.jpox.store.mapping.JavaTypeMapping#getSampleValue()
*/
public Object getSampleValue(ClassLoaderResolver clr)
{
return java.awt.Color.red;
}
/* (non-Javadoc)
* @see org.jpox.store.mapping.JavaTypeMapping#setObject(org.jpox.PersistenceManager, java.lang.Object, int[], java.lang.Object)
*/
public void setObject(PersistenceManager pm, Object preparedStatement, int[] exprIndex, Object value)
{
Color color = (Color) value;
if (color == null)
{
getDataStoreMapping(0).setObject(preparedStatement, exprIndex[0], null);
getDataStoreMapping(1).setObject(preparedStatement, exprIndex[1], null);
getDataStoreMapping(2).setObject(preparedStatement, exprIndex[2], null);
getDataStoreMapping(3).setObject(preparedStatement, exprIndex[3], null);
}
else
{
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());
}
}
/* (non-Javadoc)
* @see org.jpox.store.mapping.JavaTypeMapping#getObject(org.jpox.PersistenceManager, java.lang.Object, int[])
*/
public Object getObject(PersistenceManager pm, Object resultSet, int[] exprIndex)
{
try
{
// Check for null entries
if (((ResultSet)resultSet).getObject(exprIndex[0]) == null)
{
return null;
}
}
catch (Exception e)
{
// Do nothing
}
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);
}
// --------------------------------- JDOQL Query Methods -------------------------------------------
/* (non-Javadoc)
* @see org.jpox.store.mapping.JavaTypeMapping#newLiteral(org.jpox.store.query.QueryStatement, java.lang.Object)
*/
public ScalarExpression newLiteral(QueryExpression qs, Object value)
{
return null; // Dont support JDOQL querying of Color fields currently
}
/* (non-Javadoc)
* @see org.jpox.store.mapping.JavaTypeMapping#newScalarExpression(org.jpox.store.query.QueryStatement, org.jpox.store.expression.TableExpression)
*/
public ScalarExpression newScalarExpression(QueryExpression qs, LogicSetExpression te)
{
return null; // Dont support JDOQL querying of Color fields currently
}
}In the initialize() method 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> |