Extensions : Java Type

Plugin

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 DataNucleuss capabilities to support a particular Java type 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)
  • 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)
  • converter-name - name of a TypeConverter to use as the default way of persisting this type when it isnt directly persistable as-is. Please refer to type-converter extension point for details of how to define your own type converter
  • priority - Set this to a large number if you are overriding the default handling for a type that is supported out of the box

All of these are optional, and you should define what is required for your type.

wrapper-type

As weve 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.store.types.SCO Javadoc.

package org.mydomain;

import java.io.ObjectStreamException;
import javax.jdo.JDOHelper;
import javax.jdo.spi.PersistenceCapable;
import org.datanucleus.state.ObjectProvider;

public class MyDateWrapper extends java.util.Date implements SCO
{
    private transient ObjectProvider ownerOP;
    private transient String fieldName;

    public MyDateWrapper(ObjectProvider op, String fieldName)
    {
        super();

        this.ownerOP = op;
        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()
    {
        ownerOP = null;
    }

    public Object getOwner()
    {
        return (ownerOP != null ? ownerOP.getObject() : null);
    }

    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.

Plugin Specification

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" dfg="true" priority="10"/>
    </extension>
</plugin>

Note that the priority is specified since this type is provided by DataNucleus itself and so your mapping needs to override it. Note also that you require a MANIFEST.MF file as per the Extensions Guide.

Obviously all standard types (such as java.util.Date) already have their values defined by DataNucleus itself typically in datanucleus-core.