Overview

The API of a message-based program consists of descriptions of messages and message sequences. For the structure of the messages we could use a simple document (e. g. a spreadsheet) for a description. However, it is easier for the user to use XML.

The XML description of the messages can be passed on to anyone interested in your API. The interested programmer saves the XML files in a JAVA package of his choice and calls the Record-Generator. This is a small devel. one-tool, which generates static access classes for the messages from all record descriptions. This enables you to access the data in a record in a type-safe manner. It also works language-independent, so it is not limited to JAVA. Of course, the whole thing is optional; manual is also possible, but not so efficient.

Die Record Description

A record file can contain any number of messages. Here is an XML file describing the message to create a namespace:

<?xml version="1.0" encoding="ISO-8859-1"?>
<records>
    <record id="9f9dfda5-21aa-4fb9-a15f-5dccef2612e0" name="CREATE_NAMESPACE" isService="true">
        <description>
            Create, register and start a Namespace.
        </description>
        <namespace>SYSTEM</namespace>
        <slot keyid="" keytype="WANTED_NID" name="WANTED_NID" direction="REQUEST" mandatory="false" type="ID">
            <description>Optional: A namespace ID to use. If the argument is missing, a random numerical NID is used.</description>
        </slot>
        <slot keyid="" keytype="CREATED_NID" name="CREATED_NID" direction="ANSWER" mandatory="false" type="ID">
            <description>The namespace ID for the newly created namespace. Only on SUCCESS.</description>
        </slot>
    </record>
</records>

The message has the ID “9f9dfda5-21aa-4fb9-a15f-5dccef2612e0”. This is a UUID in string form and therefore well suited for describing services. Compared to a string or integer, a UUID is quite large (17 bytes in the stream), but collision-safe. For internal messages you can choose the narrow variant, see here.

The name of the record “CREATE_NAMESPACE” leads to the name of the class (CRecordCreateNamespace). The record is used for services (isService=true) and the service is automatically registered at the startup in the namespace “SYSTEM”.

Two slots are recorded: one for the request, the other for the answer. Both slots are of type ID (for information on the slot types, see here). For the slotkeys mostly only the keytype is used. The slot names lead to the naming of the access methods:

static IId getParamCreatedNid(CRecord aRecord, IId aDefault);
static void setParamCreatedNid(CRecord aRecord, IId aValue) throws CException;

static IId getParamWantedNid(CRecord aRecord, IId aDefault);
static void setParamWantedNid(CRecord aRecord, IId aValue) throws CException;

The record generator generates a class with which you can fill or read a record:

package de.softdevel.d1.kernel;

import de.softdevel.d1.exception.CException;
import de.softdevel.d1.id.IId;
import de.softdevel.d1.id.impl.CIdFactory;
import de.softdevel.d1.namespace.INamespace;
import de.softdevel.d1.record.CRecord;
import de.softdevel.d1.record.CRecordAccessHelper;
import de.softdevel.d1.slot.CCommonSlotType;
import de.softdevel.d1.slot.CSlotFactoryHelper;
import de.softdevel.d1.slot.CSlotKeyFactory;
import de.softdevel.d1.slot.ISlot;
import de.softdevel.d1.slot.ISlotKey;
import de.softdevel.d1.target.ITarget;

/**
 * Create, register and start a Namespace.
 */
public final class CRecordCreateNamespace
{
    /**
     * Message ID.
     */
    public static final IId ID = CIdFactory.create("9f9dfda5-21aa-4fb9-a15f-5dccef2612e0"); //$NON-NLS-1$

    /**
     * Slot CREATED_NID.<br>
     * data type = ID<br>
     * mandatory = false<br>
     * direction = ANSWER<br>
     * The namespace ID for the newly created namespace. Only on SUCCESS.
     */
    public static final ISlotKey SLOT_CREATED_NID = CSlotKeyFactory.createByObject("CREATED_NID", ""); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Slot WANTED_NID.<br>
     * data type = ID<br>
     * mandatory = false<br>
     * direction = REQUEST<br>
     * Optional: A namespace ID to use. If the argument is missing, a random numerical NID is used.
     */
    public static final ISlotKey SLOT_WANTED_NID = CSlotKeyFactory.createByObject("WANTED_NID", ""); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Namespaces if the record belongs to a Service.
     */
    public static final String[] NAMESPACES = { "SYSTEM" }; //$NON-NLS-1$

    /**
     * True if the record belongs to a Service.
     */
    public static final boolean IS_SERVICE = true;

    /**
     * Create Record with this ID.
     *
     * @return The record with this ID.
     */
    public static CRecord create()
    {
        return new CRecord(ID);
    }

    /**
     * Add an observer to the service the record belongs to.
     * Prefer the targets namespace if the service is registered in more than one namespace.
     * LOG an error if the record doesn't belong to a namespace.
     * The given target must be registered.
     *
     * @param aTarget
     *        The target of the user.
     * @return True if the observer has been added.
     */
    public static boolean addObserver(final ITarget aTarget)
    {
        return CRecordAccessHelper.addObserver(aTarget, NAMESPACES, ID);
    }

    /**
     * The service is registered in only one namespace. Get it.
     *
     * @return The namespace the service is registered in.
     */
    public static INamespace getNamespace()
    {
        return CRecordAccessHelper.getNamespace(NAMESPACES[0]);
    }

    /**
     * The service is registered in only one namespace. Get its NID.
     *
     * @return The NID of the namespace the service is registered in.
     */
    public static IId getNID()
    {
        return CRecordAccessHelper.getNID(NAMESPACES[0]);
    }

    /**
     * The namespace ID for the newly created namespace. Only on SUCCESS.
     *
     * @param aRecord
     *        The record.
     * @param aDefault
     *        Default value, if slot (data) not found.
     * @return The value if found, otherwise the default value.
     */
    public static IId getParamCreatedNid(final CRecord aRecord, final IId aDefault)
    {
        final ISlot slot = aRecord.getSlot(SLOT_CREATED_NID);
        if (slot == null)
        {
            return aDefault;
        }
        else
        {
            return (IId) slot.getValue();
        }
    }

    /**
     * Optional: A namespace ID to use. If the argument is missing, a random numerical NID is used.
     *
     * @param aRecord
     *        The record.
     * @param aDefault
     *        Default value, if slot (data) not found.
     * @return The value if found, otherwise the default value.
     */
    public static IId getParamWantedNid(final CRecord aRecord, final IId aDefault)
    {
        final ISlot slot = aRecord.getSlot(SLOT_WANTED_NID);
        if (slot == null)
        {
            return aDefault;
        }
        else
        {
            return (IId) slot.getValue();
        }
    }

    /**
     * The namespace ID for the newly created namespace. Only on SUCCESS.
     *
     * @param aRecord
     *        The record.
     * @param aValue
     *        The value to set.
     * @throws CException
     *        on parsing the value.
     */
    public static void setParamCreatedNid(final CRecord aRecord, final IId aValue) throws CException
    {
        final ISlot slot = CSlotFactoryHelper.create(CCommonSlotType.ID, aValue);
        aRecord.addSlot(SLOT_CREATED_NID, slot);
    }

    /**
     * Optional: A namespace ID to use. If the argument is missing, a random numerical NID is used.
     *
     * @param aRecord
     *        The record.
     * @param aValue
     *        The value to set.
     * @throws CException
     *        on parsing the value.
     */
    public static void setParamWantedNid(final CRecord aRecord, final IId aValue) throws CException
    {
        final ISlot slot = CSlotFactoryHelper.create(CCommonSlotType.ID, aValue);
        aRecord.addSlot(SLOT_WANTED_NID, slot);
    }

    /**
     * Private Constructor (no instances wanted).
     */
    private CRecordCreateNamespace()
    {
        // no instance
    }

}

The structure of a record description is always the same (as generated):

  • ID
  • SlotKeys
  • Getter
  • Setter

The use of the class is then quite simple. Here is an example of how to send the message:

final IId nid = CRecordCreateNamespace.getNID();
final CEnvelope env = new CEnvelope(nid);

final CRecord rec = CRecordCreateNamespace.create();
CRecordCreateNamespace.setParamWantedNid(rec, aNid);

sendRequest(env, rec);

And here is the example of the answer processing:

addMessageHandler(CRecordCreateNamespace.ID, new IMessageHandler()
{
    @Override
    public boolean handleMessage(final CEnvelope aEnvelope,
                                 final CRecord aRecord) throws Exception
    {
        if (aEnvelope.isAnswer())
        {
            final int code = aEnvelope.getResultCode();
            if (code == CResultCode.SUCCESS)
            {
                //
                // Okay, namespace created.
                //
                final IId nid = CRecordCreateNamespace.getParamCreatedNid(aRecord, null);

                ...
            }
            else
            {
                //
                // Error creating namespace.
                //

                ...
            }
        }
        return true;
    }
});

The Tool

../_images/RecordGenerator1.PNG

The generator generates a record XML database that is loaded by the framework at the startup. When this file is loaded, defined services are automatically registered in the service registry. In this tab you enter the directories where a copy of the file should be created. Since we usually create our applications in the form of plug-ins, this file should be created in the resources directory of the JAR.

../_images/RecordGenerator2.PNG

Since D1 allows the registration of own slot types, the generator requires the CLASS files of the slot classes to know these types during generation. Normally, however, this page remains empty.

../_images/RecordGenerator3.PNG

The “Search Path” is the basic directory of your project, i. e. the directory in which the “src” directory is located. The generator searches all directories for files named “record. xml” and generates the JAVA classes in the same directory.

For the analysis of the package names, the generator needs the basic directory of the sources. Usually this is probably “src” or “src/main/java” (when using Gradle).

You can then generate all classes. This has to be repeated with every change in the XML files. Fortunately, it’s very fast...

The RecordGenerator saves its settings in a H2 SQL database file. Separate values (paths) are required for each plug-in. Therefore it is advisable to start the RecordGenerator for each project in a separate (empty) directory.