Topics

Persistence APIs


In This Guide

As editors create, modify, and delete items, Brightspot initiates corresponding database save and delete life cycles. You can override callbacks in these life cycles to provide additional features, such as sending confirmation or error messages to the UI.


Save life cycle

As editors create and modify items, Brightspot makes corresponding changes in the database. You can override callbacks to provide additional functionality during the save life cycle. For example, when Brightspot successfully saves an item, you can display a notification in the content edit form.

Understanding the save life cycle

The save life cycle starts with either of the following methods:

  • Record#save—Calling this method performs a standard save.
  • Record#saveImmediately—Calling this method saves the object outside the context of a transaction.

The following diagram shows the various callbacks you can use during this life cycle.

Save life cycle

Referring to the previous diagram—

  • The available callbacks beforeSaveonValidatebeforeCommitonDuplicate, and afterSave have empty implementations in Record, so you can override them in your own classes.
  • save and saveImmediately are not available callbacks, and cannot be overridden.
Note
The save life cycle incurs overhead for callbacks and validation. If you want to save data without validation, such as during a large ingestion, save objects with the Record#saveUnsafely method.

Save life cycle callbacks

This section describes the various callbacks during the extended save life cycle. These callbacks correspond to the diagram "Save life cycle."

Note
If you need to save or delete an object in the following callbacks, always use saveImmediately or deleteImmediately. If you need to save or delete multiple objects, consider using a database transaction (see Transactions) with Database#beginIsolatedWrites.

beforeSave

Use beforeSave for simple state changes and low-overhead database or API calls. In particular, you can use this method to modify data before the validation step in the save life cycle. One common use for beforeSave is to populate hidden fields. Because editors cannot directly populated these fields in the UI, you must populate them in code.

import com.psddev.cms.db.Content;
import com.psddev.cms.db.ToolUi;

public class Article extends Content {

    private String name;

    private String code;

    /* Declare a hidden field. */
    @ToolUi.Hidden
    private String internalName;

    public String getName() {
        return name;
    }

    public String getCode() {
        return code;
    }

    /* Populate a hidden field. */
    @Override
    public void beforeSave() {
        this.internalName = getName() + "-" + getCode();
    }
}

onValidate

Use onValidate for custom validation on an object beyond what the database or annotations supply, such as sending customized messages to the UI.

In the following snippet, the name field is annotated with @Recordable.Required. If an editor saves an item with a blank name, Brightspot displays a standard error message. The snippet overrides the onValidate callback to display a custom error message.

import com.psddev.cms.db.Content;

public class Article extends Content {

    @Required
    private String name;

    /* Display custom error message. */
    protected void onValidate() {
        if (name == null) {
            getState().addError(getState().getField("name"), "Enter a name.");
        }
    }
}

Error messages for an empty required field

Standard error message
Standard error message
Custom error message
Custom error message

See also:

beforeCommit

Use beforeCommit to make additional changes to the object that do not require validation. Examples include ensuring certain fields are null, prepending or appending strings, or populating fields with valid non-null values.

In the following example, the beforeSave method sets the lastUpdated field to the current date and time.

import com.psddev.cms.db.Content;
import com.psddev.cms.db.ToolUi;

public class Article extends Content {

    @ToolUi.Hidden
    private Date lastUpdated;

    protected void beforeCommit() {
        lastUpdated = new Date();
    }
}

onDuplicate

Use onDuplicate or provide custom logic when the database save operation fails because a uniqueness violation is detected on an indexed field or method. The most common uses of this method are the following:

  • Resolve the violation by setting a unique value and return true. In this case the save life cycle returns to the onValidate method.
  • Display a custom message in the content edit form and return false. In this case the save life cycle returns to the beforeSave method.

In the following example, the headline field is annotated with @Indexed(unique = true). If an editor saves an item with an existing headline, Brightspot displays a standard error message. The following snippet overrides the onDuplicate callback to display a custom error message.

import com.psddev.cms.db.Content;
import com.psddev.dari.db.ObjectIndex;

public class Article extends Content {

    @Indexed(unique = true)
    private String headline;

    /* Display custom error message. */
    protected boolean onDuplicate(ObjectIndex index) {
        getState().addError(getState().getField("headline"), "Another article has this headline. Use a different headline.");
        return false;
    }

}

Error messages for duplicate values

Standard duplication error message
Standard duplication error message
Custom duplication error message
Custom duplication error message

See also:
@Recordable.Indexed

afterSave

Use afterSave to provide processing after the database save, typically for sending messages to the UI or to log files. The following snippet overrides the afterSave callback to write a message to stdout.

import com.psddev.cms.db.Content;

public class Article extends Content {

    private String headline;

    public String getHeadline() {
        return headline;
    }

    /* Write message to stdout. */
    protected void afterSave() {
        System.out.println("Saved an article with the following headline: " + getHeadline());
    }

}

27-Feb-2020 14:32:06 INFO Saved an article with the following headline: After 300 years, Astronaut Higgins escapes from black hole. Doesn't look a day older.


Delete life cycle

As editors delete items in the UI, Brightspot deletes corresponding records in the database. You can override callbacks to provide additional functionality during the delete life cycle. For example, when Brightspot successfully deletes an item, you can write a message to a log file.

Understanding the delete life cycle

The delete save life cycle starts with the Record#delete method. The following diagram shows the various callbacks you can use during this life cycle.

delete_life_cycle.svg
Delete life cycle

Referring to the previous diagram—

  • The available callbacks beforeDelete and afterDelete have empty implementations in Record, so you can override them in your own classes.
  • delete is not an available callback, and cannot be overridden.

Delete life cycle callbacks

This section describes the various callbacks during the delete life cycle. These callbacks correspond to the diagram "Delete life cycle."

Note
If you need to save or delete an object in the following callbacks, always use saveImmediately or deleteImmediately. If you need to save or delete multiple objects, consider using a transaction (see Transactions) with Database#beginIsolatedWrites.

beforeDelete

Use beforeDelete to provide validation, referential integrity, or messaging before actually deleting an object. The following snippet deletes all of an author’s articles from the database before deleting the author.

import com.psddev.cms.db.Content;
import com.psddev.dari.db.Query;

public class Author extends Content {

    /* Delete all of an author's articles before actually deleting the author. */
    @Override
    protected void beforeDelete() {
        Query.from(Article.class).where("author = ?", this.getId()).deleteAll();
    }
}

afterDelete

Use afterDelete to provide processing after the database delete, typically for sending messages to the UI or to log files. The following snippet overrides the afterDelete callback to write a message to stdout.

import com.psddev.cms.db.Content;

public class Author extends Content {

    @Override
    protected void afterDelete() {
        System.out.println("Deleted an author with ID " + this.getId());
    }
}

27-Feb-2020 15:41:38 INFO Deleted an author with ID 00000170-8861-d763-a7f7-fff91cee0000