8.7. Java Domain Objects

Each object type will have a corresponding domain class written in Java. The domain class exists to provide a Java-native API to your object type's data and to behaviors that are specific to your application. Refer to Chapter 2 WAF Component: Persistence.

package com.example.binder;

import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.util.Assert;
import com.arsdigita.web.Application;
import org.apache.log4j.Logger;

/**
 * A collection of <code>Note</code>s.
 *
 * @see com.example.binder.Note
 * @author Justin Ross
 */
public class Binder extends Application {
    private static final Logger s_log = Logger.getLogger(Binder.class);

    public static final String BASE_DATA_OBJECT_TYPE =
        "com.example.binder.Binder";

    static final String NOTES = "notes";

    protected String getBaseDataObjectType() {
        return BASE_DATA_OBJECT_TYPE;
    }

    protected Binder(final DataObject data) {
        super(data);
    }

    /**
     * Returns all the Notes associated with this Binder.
     *
     * @return A <code>NoteCollection</code> of Note objects
     */
    public final NoteCollection getNotes() {
        return new NoteCollection((DataCollection) get(NOTES));
    }

    /**
     * Adds <code>note</code> to the set of notes tracked by this
     * <code>Binder</code>.
     *
     * @param note The <code>Note</code> to remove
     */
    public void addNote(final Note note) {
        Assert.exists(note, Note.class);

        if (s_log.isDebugEnabled()) {
            s_log.debug("Adding note " + note + " to " + this);
        }

        add(NOTES, note);
    }

    /**
     * Removes <code>note</code> from the set of notes tracked by this
     * <code>Binder</code>.
     *
     * @param note The <code>Note</code> to remove
     */
    public void removeNote(final Note note) {
        Assert.exists(note, Note.class);

        if (s_log.isDebugEnabled()) {
            s_log.debug("Removing note " + note + " from " + this);
        }

        remove(NOTES, note);
    }

    public final String getContextPath() {
        return "/binder";
    }
}

Example 8-3. binder/src/com/example/binder/Binder.java

The BASE_DATA_OBJECT_TYPE string specifies what PDL-defined object type this domain object corresponds to.

The getBaseDataObjectType() method makes the runtime data object type of Binder accessible to WAF persistence.

The DataObject constructor is a bit of plumbing necessary to make your DomainObject work, since a DomainObject wraps a DataObject.

Everything else on Binder is there merely to expose the properties of the Binder object type as defined in the PDL above. Binder's UI will use the API defined here to fetch data from and to alter Binder objects.

NoteNote
 

Binder.java uses string constants that correspond to PDL-defined properties. This way the Java compiler will catch any typos where the property is used, and a change to the PDL-defined property name requires changing the Java code in just one place.

Note

It's a good idea for any WAF application to use logging to monitor important events in the lifecycles of its objects. In the above example, any time a note is added or removed, we log what happened. If down the line a bug appears and it's unclear whether notes are getting added to the binder, the developer debugging the code can simply enable correct logging and refer to whether addNote is getting called. For more information about logging in WAF, refer to Section 7.5 Using logging for debugging.

package com.example.binder;

import com.arsdigita.db.Sequences;
import com.arsdigita.domain.DomainObject;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.util.Assert;
import com.arsdigita.util.UncheckedWrapperException;
import java.io.IOException;
import java.io.Writer;
import java.sql.SQLException;
import java.math.BigInteger;
import org.apache.log4j.Logger;

/**
 * A bit of text with a title.
 *
 * @see com.example.binder.Note
 * @author Justin Ross
 */
public class Note extends DomainObject {
    private static final Logger s_log = Logger.getLogger(Note.class);

    public static final String BASE_DATA_OBJECT_TYPE =
        "com.example.binder.Note";

    static final String ID = "id";
    static final String TITLE = "title";
    static final String BODY = "body";

    protected String getBaseDataObjectType() {
        return BASE_DATA_OBJECT_TYPE;
    }

    protected Note(final DataObject data) {
        super(data);

        if (isNew()) {
            try {
                set(ID, Sequences.getNextValue().toBigInteger());
            } catch (SQLException sqle) {
                throw new UncheckedWrapperException(sqle);
            }
        }
    }

    /**
     * Gets the note's unique ID.
     *
     * @return The <code>BigInteger</code> ID; it cannot be null
     */
    public final BigInteger getID() {
        return (BigInteger) get(ID);
    }

    /**
     * Gets the title of the note.
     *
     * @return The <code>String</code> title; it cannot be null
     */
    public final String getTitle() {
        return (String) get(TITLE);
    }

    /**
     * Sets the title of the note.
     *
     * @param title The <code>String</code> title; it cannot be null
     */
    public final void setTitle(final String title) {
        if (s_log.isDebugEnabled()) {
            s_log.debug("Setting title of " + this + " to " + title);
        }

        Assert.exists(title, String.class);

        set(TITLE, title);
    }

    /**
     * Gets the body of the note.
     *
     * @return The <code>String</code> body; it may be null
     */
    public final String getBody() {
        return (String) get(BODY);
    }

    /**
     * Sets the body of the note.
     *
     * @param body The <code>String</code> body; it may be null
     */
    public final void setBody(final String body) {
        if (s_log.isDebugEnabled()) {
            s_log.debug("Setting body of " + this + " to " + body);
        }

        if (Assert.isEnabled() && body != null) {
            Assert.truth(body.length() <= 4000, "The body text is too long");
        }

        set(BODY, body);
    }

    /**
     * Writes an XML representation of the note and its content.
     *
     * @param out A <code>Writer</code> to write the XML to
     */
    public final void render(final Writer out) throws IOException {
        out.write("<note id=\"" + getID() + "\">");
        out.write("<title>" + getTitle() + "</title>");

        final String body = getBody();

        if (body != null) {
            out.write("<body>" + getBody() + "</body>");
        }

        out.write("</note>");
    }
}

Example 8-4. binder/src/com/example/binder/Note.java

In many ways, Note.java is just like Binder.java. It declares its data object type; it defines an instantiator; and it adds some plumbing for getting the runtime data object type and for constructing a wrapper DomainObject.

Since the properties of the data object type differ from those of Binder, the Java accessors and mutators are different. Of more interest is the fact that Note has additional business logic for outputting the Note object as XML.

NoteNote
 

The setTitle method uses assertions to enforce the contract set out in the object-type definition. The title property is marked [1..1] in Note.pdl, so it cannot have a null value. Therefore, we assert that calling setTitle(null) is a failure condition.

NoteNote
 

If your code includes assertions that do work in their arguments, make sure to wrap the assertion code in an if statement that checks that assertions are turned on:

        if (Assert.isEnabled() && body != null) {
            Assert.truth(body.length() <= 4000, "The body text is too long");
        }

Example 8-5. Using assertions

Be careful, however, that this is what you really want. Assertions exist to detect unexpected, unrecoverable program failures. Don't use them for other types of errors, such as user input validation, since they can be disabled.

The WAF persistence API gives us efficient cursor-oriented access to the elements of an association. The NoteCollection object gives our UI code access to the data for each row returned from a query for multiple Notes.

package com.example.binder;

import com.arsdigita.domain.DomainCollection;
import com.arsdigita.persistence.DataCollection;
import org.apache.log4j.Logger;

/**
 * Represents a collection of {@link com.example.binder.Note notes}.
 *
 * @author Justin Ross
 */
public class NoteCollection extends DomainCollection {
    private static final Logger s_log = Logger.getLogger(NoteCollection.class);

    public NoteCollection(final DataCollection data) {
        super(data);
    }

    /**
     * Gets the title of the current note.
     *
     * @return The <code>String</code> title; it cannot be null
     */
    public final String getTitle() {
        return (String) get(Note.TITLE);
    }

    /**
     * Gets the body of the current note.
     *
     * @return The <code>String</code> body; it may be null
     */
    public final String getBody() {
        return (String) get(Note.BODY);
    }
}

Example 8-6. binder/src/com/example/binder/NoteCollection.java