Plugins : Java Type

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.





wrapper-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.

string-converter

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.



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" 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 .