|
DataNucleus provides capabilities for persistence of particular Java types. Some types are
by default persistent, some are by default in the default "fetch-group". Similarly some are
second class mutable, and hence have their operations intercepted. An extension-point is
available to define other Java types in this way.
You can extend DataNucleus's capabilities using the plugin extension
org.datanucleus.java_type
.
The attributes that you can set for each Java type are
-
dfg
- whether this type is by default in the default-fetch-group (true/false)
-
persistent
- whether this type is, by default, persistent (true/false)
-
embedded
- whether this type is, by default, embedded (true/false)
-
wrapper-type
- class name of the SCO wrapper (if it needs a wrapper)
-
wrapper-type-backed
- class name of a SCO wrapper (with backing store)
-
java-version
- version of JDK that this applies to (if specific only)
-
java-version-restricted
- whether this is restricted to a particular version of JDK
-
string-converter
- name of a class that can
convert this type to/from String
All of these are optional, and you should define what is required for your type.
As we've mentioned above, if a java type is considered
second class mutable
then it
needs to have any mutating operations intercepted. The reason for this is that DataNucleus
needs to be aware when the type has changed value internally. To give an example of such a type
and how you would define support for intercepting these mutating operations lets use
java.util.Date
. We need to write a
wrapper
class. This has to be castable
to the same type as the Java type it is representing (so inherited from it).
So we extend "java.util.Date", and we need to implement the
interface
org.datanucleus.sco.SCO
package org.mydomain;
import java.io.ObjectStreamException;
import javax.jdo.JDOHelper;
import javax.jdo.spi.PersistenceCapable;
import org.datanucleus.StateManager;
public class MyDateWrapper extends java.util.Date implements SCO
{
private transient StateManager ownerSM;
private transient Object owner;
private transient String fieldName;
public MyDateWrapper(StateManager ownerSM, String fieldName)
{
super();
if (ownerSM != null)
{
this.ownerSM = ownerSM;
this.owner = ownerSM.getObject();
}
this.fieldName = fieldName;
}
public void initialise()
{
}
/** Method to initialise the SCO from an existing value. */
public void initialise(Object o, boolean forInsert, boolean forUpdate)
{
super.setTime(((java.util.Date)o).getTime());
}
/** 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();
}
/** Accessor for the unwrapped value that we are wrapping. */
public Object getValue()
{
return new java.util.Date(getTime());
}
public Object clone()
{
Object obj = super.clone();
((Date)obj).unsetOwner();
return obj;
}
public void unsetOwner()
{
owner = null;
ownerSM = null;
fieldName = null;
}
public Object getOwner()
{
return owner;
}
public String getFieldName()
{
return this.fieldName;
}
public void makeDirty()
{
if (ownerSM != null)
{
ownerSM.getObjectManager().getApiAdapter().makeFieldDirty(owner, fieldName);
}
}
public Object detachCopy(FetchPlanState state)
{
return new java.util.Date(getTime());
}
public void attachCopy(Object value)
{
long oldValue = getTime();
initialise(value, false, true);
// 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());
}
}
So we simply intercept the mutators and mark the object as dirty in its StateManager.
If your Java type is not able to be persisted natively by a datastore (for example
a URL is not storable as a URL type in an Excel spreadsheet) then you could provide
a way of storing it as a String since all datastores allow Strings to be stored.
Let's take an example. We have a java type
java.net.URL
. We want to provide a
string-converter
. We need to implement
org.datanucleus.store.types.ObjectStringConverter
So we define our converter like this
public class URLStringConverter implements ObjectStringConverter
{
public Object toObject(String str)
{
if (str == null)
{
return null;
}
URL url = null;
try
{
url = new java.net.URL(str.trim());
}
catch (MalformedURLException mue)
{
throw new NucleusDataStoreException(
"Error converting String \"" + str + "\" to URL", mue);
}
return url;
}
public String toString(Object obj)
{
String str;
if (obj instanceof URL)
{
str = ((URL)obj).toString();
}
else
{
str = (String)obj;
}
return str;
}
}
So as simple as it could possibly be. We implement method
toObject(String)
and
toString(Object)
. The vast majority of java types could provide this type
of conversion as a fallback if the datastore doesn't handle their type natively.
To define the persistence characteristics of a Java type you need to add entries to
a
plugin.xml
file at the root of the CLASSPATH. The file
plugin.xml
will
look like this
<?xml version="1.0"?>
<plugin id="mydomain.mystore" name="DataNucleus plug-ins" provider-name="My Company">
<extension point="org.datanucleus.java_type">
<java-type name="java.util.Date" wrapper-type="mydomain.MyDateWrapper" persistent="true" dfg="true"/>
</extension>
</plugin>
Note that you also require a MANIFEST.MF file as per the
Plugins Guide.
Obviously all standard types (such as
java.util.Date
) already have their values
defined by DataNucleus itself typically in
datanucleus-core
.
|
|