Brightspot CMS Developer Guide

Data modeling annotations


The Dari framework provides Java annotations that enhance the behavior or the processing of model classes or fields. For example, the @DisplayName annotation substitutes descriptive names in place of actual field or class names in the UI. The @Indexed annotation triggers runtime indexing of fields or methods so that they can be queried.

This topic shows you how to use Dari annotations.

As shown in the following example, a class annotation is placed above the class declaration, whereas a field annotation can be placed above the target field or beside the field declaration.

@Recordable.BootstrapPackages("System Activities")
public class Activity extends Record {

    @Indexed
    private Date activityDate;

    @Required private User activityUser;

    private Project project;
    private String type;

 }



Applies to: Class

Denotes that the objects referenced by fields in the target type are lazily loaded. For example, to facilitate smooth browsing through a photo gallery with several images, image initialization is deferred until the point at which it is needed.


Applies to: Class

Specifies an array of classes that the target type should modify. This takes precedence over the T type argument in Modification. In the following, Bar would apply only to Foo, not Object:

@Modification.Classes({ Foo.class })
class Bar extends Modification<Object> {

}

For more information, see Modifications.


Applies to: Annotation

Specifies the processor class that can manipulate a field definition using the target annotation. For an annotation applied to a field, ObjectField.AnnotationProcessorClass specifies the implemented class that Dari will use to processes the field. For a custom annotation, you implement ObjectField.AnnotationProcessor.


Applies to: Annotation

Specifies the processor class that can manipulate a class using the target annotation. For an annotation applied to a class, ObjectType.AnnotationProcessorClass specifies the implementation that Dari will use to processes the class. For a custom annotation, you implement ObjectType.AnnotationProcessor.


Applies to: Class

Similar to specifying a Java class as abstract, this annotation prevents instances of the target type from being written to the database. The underlying ObjectType will be created, but only when ObjectType#isConcrete is true can an object be saved to the database.

In the example below, the Project class can be created, but only subclassed instances of Project can be saved to the database:

@Abstract
public class Project extends Record {

}



Applies to: Class

For use with JSPs, this annotation specifies the JavaBeans property name that can be used to access an instance of the target type as a modification. It is useful if you need to add new fields to existing objects and the fields will be rendered.

For example, say that you need to add two fields, promoTitle and promoImage, to a group of objects. You would create an interface that classes requiring the new fields will implement. The interface includes a static class that extends Modification and declares the new fields. The @BeanProperty annotation specifies the name by which the static class will be accessed.

public interface Promotable extends Recordable {

    @BeanProperty("promotable")
    public static class DefaultPromotable extends Modification<Promotable> {

        @Indexed
        private String promoTitle;
        private Image promoImage;

        /* Getters and setters */

    }
}


Any classes that require the new fields must be updated to implement Promotable, with the new fields added.

public class Blog extends Content implements Promotable {

    private String promoTitle;
    private Image promoTitle;

    /* Getters and setters */
}


To access the newly added fields when rendering the content, the @BeanProperty("promotable") is used:

<cms:render value="${content.promotable.promoTitle}" />
<cms:img src="${content.promotable.promoImage}" />



Applies to: Field

When the @Recordable.BootstrapPackages annotation is applied to a class, @BootstrapFollowReferences can be applied to fields that reference Record-derived objects. This annotation enables the referenced objects to be exported to the package specified in the @BootstrapPackages annotation. The Database Bootstrap tool performs export and import operations between Brightspot environments with the same underlying data model.

Note
@BootstrapFollowReferences enables only referenced objects to be exported to the package specified in the @BootstrapPackages annotation. Other objects of the same data type as the referenced objects are not exported. This export behavior contrasts with the use of the depends element in the @BootstrapPackages annotation.

In the following example, the @BootstrapPackages annotation enables Activity objects to be exported to a package called System Activities. Two fields of the target class reference Record-derived types, User and Project, and are targets of the @BootstrapFollowReferences annotation. Therefore, all objects of type User and Project that are referenced by Activity objects will be exported along with the Activity objects.

@Recordable.BootstrapPackages("System Activities")
 public class Activity extends Record {

    private Date activityDate;
    private String activityType;

    @Recordable.BootstrapFollowReferences
    private User activityUser;

    @Recordable.BootstrapFollowReferences
    @private Project project;

    /* Getters and setters */
 }



Applies to: Class

Enables objects of the target class to be exported to a custom package identified by the value element. This annotation is used in conjunction with the Database Bootstrap tool, which performs export and import operations between Brightspot environments with the same underlying data model. Target classes to which this annotation is applied are listed in the Database Bootstrap tool.

The required value element specifies one or more packages to export object data. A package is a JSON file that can be downloaded from the Database Bootstrap tool. A separate JSON file is created for each package specified in the annotation.

The optional depends element specifies data types on which the target class is dependent. The specified data types will appear in a selection list in the Database Bootstrap tool. The selection list is associated with the packages set in the value element. This allows the tool user to select the dependent types to export with the objects of the target class.

Note
All objects of the types specified in the depends element are eligible for export, which could be many more objects than are actually referenced by instances of the target class. For example, if the target class references two object types, then each instance of the class references two objects. If you specify those same object types in the depends element, all objects of those types can be selected for export, even if some of those objects are not referenced by instances of the target class. To limit dependencies to only referenced objects, see the @BootstrapFollowReferences annotation.


In the following example, the annotation enables Activity objects to be exported to a package called System Activities. In addition, the User and Project types will appear in a selection list in the Database Bootstrap tool. If the tool user selects those types, then all User and Project objects will be exported, including those that are not referenced by Activity objects.

@Recordable.BootstrapPackages(value={"System Activities"}, depends={User.class, Project.class}
public class Activity extends Record {

    private Date activityDate;
    private String activityType;

    private User activityUser;
    private Project project;

    /* Getters and setters */
}



Applies to: Field

Supported on any subclass of Collection, this annotation specifies the maximum number of items in the target collection. In the following example, no more than eight Slide objects can be added to the list.

public class Gallery extends Content {

    @CollectionMaximum(8)
    private List<Slide> slides;

}



Applies to: Field

Supported on any subclass of Collection, this annotation specifies the minimum number of items in the target collection. In the following example, at least one Slide object must be added to the list.

public class Gallery extends Content {

    @CollectionMinimum(1)
    private List<Slide> slides;

}



Applies to: Field, Class

For Solr indexing purposes only, this annotation specifies whether a reference field is denormalized within instances of a referring class. Denormalizing forces Dari to copy data of referenced objects to instances of the referring class. The denormalized data is saved in the Solr index; it is not visible on the referring object. If set on a class, the annotation applies to all of the reference fields in the class.

Note
The annotation is reserved for advanced cases. Only use when it is absolutely necessary.

This annotation is useful in site searches where the query criteria specifies data from both referenced and referring objects. In the following example, the Person class references the State class with a field marked with the @Denormalized annotation.

/* Referenced class */
public class State extends Record {

    @Indexed
    private String stateName;

    @Indexed(unique = true)
    private String stateAbbreviation;

    /* Getters and setters */
}

/* Referring class */
public class Person extends Record {

    @Indexed
    private String firstName;

    @Indexed
    private String lastName;

    @Indexed
    @Denormalized
    private State state;

    /* Getters and setters */
}

In the Solr index, Dari copies the data of State objects to the referring Person objects. This allows Solr to find Person objects based on text stored in Person objects and on text in referenced State objects. For example, the following query searches the Solr index for Person objects with a string of “Gena Roberts”, which is stored in a Person object, and a string of “North Dakota”, which is stored in a State object that is referenced by the Person object.

"Gena Roberts AND North Dakota"

If the @Denormalized annotation is not applied in the Person class, then the above Solr query would fail to retrieve any Person objects.


Applies to: Field, Class, Method

Specifies the target’s name, which the front end of an application can optionally display to end users. In the following example, the annotation is applied to the title field, which can be displayed as “Headline” to end users.

public class Article extends Content {

    @Recordable.DisplayName("Headline")
    private String title;

}


When the annotation is applied to a class, then the specified name can be used for display in place of the class name.

The annotation can also be applied to an @Indexed method that returns a calculated value (not one entered by a user). By default, values returned by indexed methods are unavailable for display in the front end. However, if @DisplayName is applied to an indexed method, a front end can display the calculated value with the specified name. For more information on indexing, see Indexes.


Applies to: Field, Class

Specifies whether the target class type is embedded within another class type. That is, an embedded type is not stored as a separate record in the underlying database, but is embedded in the record of the containing object. Embedded objects cannot be directly instantiated, saved, or queried. They must be accessed through the containing object.

In the following example, Contact is embedded within Company.

public class Contact extends Record {

    private String poBox;
    private String city;
    private String state;
    private String zip;

}

public class Company extends Record {

    @Embedded
    private Contact contact;

}

As an alternative to placing the annotation on an object field, you can apply the @Embedded annotation on a nested class.

public class Company extends Record {

    private String name;
    private Contact contact;

    @Embedded
    public class Contact extends Record {

        private String poBox;
        private String city;
        private String state;
        private String zip;
   
    }

}

For more information on embedded objects, see Relationships.


Applies to: Field, Class, Method

Specifies the prefix for the internal names of all fields in the target type. For example, if you have an annotated class…

@Recordable.FieldInternalNamePrefix("company-")
public class Company extends Record {

    private String name;
    private String city;
    private String zip;

}


…the name of each field in an object is prefaced with the specified annotation value, as in this JSON representation of a Company object:

{
   "company-name": "Acme, Inc.",
   "company-city": "Detroit",
   "company-zip": "20611",
   "_id": "0000015b-2022-d3a6-afdb-aba3d90f0000",
   "_type": "0000015b-2015-dfff-abdf-f89d49330000"
}

For more information on embedded objects, see Relationships.


Applies to: Field, Method

Specifies whether the target field or method should be ignored by the front end of an application (the default is false for fields and true for methods). For fields, the @Ignored annotation has a similar impact as the Java transient keyword. An ignored field is excluded from the ObjectType definition for the class, from data validation, and from queries. Values of an ignored field are not saved to the database.

You can also add @Ignored(false) to a method, which creates an ObjectMethod.


Applies to: Field, Method

Specifies whether the target field value is indexed, allowing the field to be queried. Fields can be returned and rendered without indexing, but to query on a field, it must be indexed.

Note
Apply the @Indexed annotation only to fields likely to be queried. Adding this annotation to all the fields on every class potentially creates unnecessary rows in the underlying database, and can lead to poor performance in systems with large amounts of data.

In the example below, the @Indexed annotation is applied to the userName field.

public class User extends Record {

    @Indexed
    private String userName;

}


Indexing this field allows User instances to be queried for specified names.

User user = Query.from(User.class).where("userName = 'Curly'".trim()).first();

You can apply the @Indexed annotation to getter methods or any method that returns a value. Indexing methods provides a means to index and query values that are not directly set by users, but are generated from other values stored in an object. Applying the @Indexed annotation to a method creates an ObjectMethod.

For more information on indexing, see Indexes. To reindex existing objects, see Database bulk operations.

Optional elements


caseSensitive

Default=false

If true, only case-sensitive searches are performed on the field.

unique

Default=false

If true, only a unique value can be set on the field.

visibility

Default=false

If true, an object’s visibility in a query is determined by whether its visibility-indexed fields are set to null. The @Indexed(visibility=true) annotation is typically applied to fields of type Boolean. In the following snippet, status is a visibility-indexed field.

public class Application extends Record {

    @Indexed(visibility = true)
    private Boolean status; /* set to null by default */

}

If any visibility-indexed field of an object is set to a non-null value, then that object is not returned in a query for published objects. If all of the visibility-indexed fields of the object are null, then that object is returned in a query.

For more information, see Excluding records from queries with visibility labels.


Applies to: Field, Method, Class

Specifies the identifier Dari uses to store the annotated item in its internal representation.

Without this annotation, Dari stores a class and its fields using native Java identifiers as described in the following example.

import com.psddev.cms.db.Content;

public class Author extends Content {

    private String lastName;

    private String firstName;

}

Dari’s JSON representation of this class uses the native Java identifiers.

{
  "name": "Author", 
  "internalName": "content.blog.Author", 
  "fields": [ { 
    "name": "lastName"
  }, {
    "name": "firstName"
  } ],
  "java.objectClass": "content.blog.Author" 
}
  • Class’s name
  • Class’s internal name constructed as a standard fully qualified class name.
  • Class’s fields.
  • Class’s native fully qualified Java name.

The following snippet adds @Recordable.InternalName annotations to the class name and a field.

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

@Recordable.InternalName("romance")
public class Author extends Content {

    @Recordable.InternalName("pseudonym")
    private String lastName;

    private String firstName;

    /* Getters and setters */

}


Given the annotations, Dari adjusts its internal representation of the class.

{
  "name": "Author",
  "internalName": "romance", 
  "fields": [ {
    "name": "pseudonym" 
  }, {
    "name": "firstName"
  } ],
  "java.objectClass": "content.blog.Author"
}
  • Class’s internal name constructed from the value of the annotation @Recordable.InternalName at the class level.
  • Field’s internal name constructed from the value of the annotation @Recordable.InternalName at the field level.

If you use @InternalName on an indexed field, you must use the internal name to query the field.

You can also annotate indexed methods with @InternalName.

In the following example, the getFullName method is indexed in the Author class. When an Author object is created, Dari automatically executes the method and saves the full name in the database.

@Indexed
@InternalName("fullName")
public String getFullName() {
    return (getFirstName()+ " " + getLastName());
 }


To query an indexed method that is annotated with an internal name, you must specify the method’s annotated name. The following snippet illustrates how to search for an Author object where the full name is Al Falfa using the annotation from the previous snippet.

return Query.from(Author.class).where("fullName = 'Al Falfa'").first();


The strings used with @InternalName on methods and fields within a class must be unique, otherwise the application fails to load.

See also:


Applies to: Class

Specifies one or more field names that are used to construct a displayable label for retrieved objects. By default, Dari uses the value of the first field in the class. For more information, see Object labels.


Applies to: Field

Specifies either the maximum numeric value or string length of the target field. For example, the rate field can have a value no greater than 10%.

public class Interest extends Record {

    @Maximum(.10)
    private double rate;

}


This annotation can also be used with @Recordable.Minimum and @Recordable.Step.


Applies to: Field

Specifies the valid MIME type for the target StorageItem field using the SparseSet representation. For example, the following annotation limits uploads in the field file to PDFs and images.

@MimeTypes("+application/pdf +image/")
private StorageItem file;

You can also set global limits on file upload types; for details, see Limiting uploads by file type.


Applies to: Field

Specifies either the minimum numeric value or string length of the target field. For example, the rate field can have a value no less than 1%.

public class Interest extends Record {

    private boolean dealerCharged;

    @Maximum(.10)
    @Minimum(.01)
    private double rate;

}


This annotation can also be used with @Recordable.Maximum and @Recordable.Step.

Note
Minimum or maximum field validation is only enforced on non-null values. If you want to require a value, also use the @Required annotation.



Applies to: Class

Indicates the StorageItem field that should be used by Brightspot to preview a Record instance. The StorageItem to use for preview can then be accessed via State#getPreview. The annotation is typically used on image or video classes.

For example, @PreviewField is used on the Image class, specifying the file field to use in a Brightspot preview:

@Recordable.PreviewField("file")
public class Image extends Content {

    private String name;
    private StorageItem file;
    private String altText;

}



Applies to: Method

Specifies how often the Dari background task, RecalculationTask, recalculates an indexed method and updates the method index with the returned values. An indexed method is one that returns a calculated value (not one entered by a user). Settings are based on the com.psddev.dari.db.RecalculationDelay interface. If you do not apply this annotation, Dari recalculates the method only when an object containing the method is created or saved. If you apply this annotation without parameters, the default setting for an indexed method is RecalculationDelay.Hour. That is, Dari recalculates the return value of the method and updates the index in hourly intervals. Alternatively, you can change the delay value of the annotation.

In the following example, the getFullName method is recalculated in hourly intervals.

/* Creates a full name value from the firstName and lastName values. */
@Indexed
@Recalculate
public String getFullName() {
    return (site.getFirstName()+ " " + site.getLastName());
}

For more information about indexing, see @Recordable.Indexed. For configuring the Dari Recalculation task, see Recalculation tasks.


Applies to: Field

This annotation specifies a regular expression pattern for a target string field. The input value on the field must match the pattern; otherwise, Brightspot displays an error. The regular expression pattern is set on the required value element. Optionally, you can include the validationMessage element to specify a custom error message in Brightspot.

In the following snippet, the Regex annotation specifies the required pattern for the email field. The validationMessage element is set to the Brightspot error message.

public class Author extends Content {

    private String name;

    @Recordable.Regex(value=".+\\@.+\\..+", validationMessage="Use email format 'myemail@address.com'")
    private String email;

    /* Getters and setters */
}

If a user enters a non-compliant string on the email field, Brightspot displays the message set on the validationMessage element.

Rendered validation message Rendered validation message



Applies to: Method

Provides a mechanism to accommodate changes in data models. For example, a refactor changed a parent class's field from a primitive data type to a child class; this method examines all existing instances of the parent class and updates the field to the child class.

To effect these changes, the following occurs:

  1. At deploy time, this annotation places the method in a list containing other methods with this annotation.
  2. Brightspot runs all the methods in the list (specific to the current class) when either of the following events occur:
    • On load of the content edit form (ToolPageContext#findOrReserve)
    • On actual save (AbstractDatabase#write)

As these events are quite common, add a null check or other test to avoid redundant execution.

In the following snippet, the fields email and phone are string members of ExternalTeamMember. After a refactoring effort, those two fields were incorporated into a separate class TeamMemberContactInformation. The @Relocation annotation forces Brightspot to do the following for each instance of ExternalTeamMember:

  1. Create a new instance of TeamMemberContactInformation.
  2. Populate TeamMemberContactInformation with the existing phone and email.
  3. Set the existing phone and email to null.
public class ExternalTeamMember extends Content implements AssignmentDeskContent, TeamMember {

    @Deprecated
    private String email;

    @Deprecated
    private String phone;
    
    private TeamMemberContactInformation contactInformation;
    
    @Relocate
    public TeamMemberContactInformation getContactInformation() {
        if (email != null) {
            if (contactInformation == null) {
                contactInformation = new TeamMemberContactInformation();
            }
            contactInformation.setEmail(email);
            email = null;
        }
        if (phone != null) {
            if (contactInformation == null) {
                contactInformation = new TeamMemberContactInformation();
            }
	        contactInformation.setPhone(phone);
	        phone = null;
        }
        return contactInformation;
    }
}
  • Deprecates the field email.
  • Deprecates the field phone.
  • Introduces a refactored member of type TeamMemberContactInformation that replaces the deprecated fields phone and email.
  • Places the method getTeamMemberEmail onto a list of methods to run when the load or save triggers occur.
  • Method to run when the load or save triggers occur.
  • If this object has an email, create a new instance of TeamMemberContactInformation (if one doesn't already exist), and assign that instance's email to the current email.
  • If this object has a phone, create a new instance of TeamMemberContactInformation (if one doesn't already exist), and assign that instance's phone to the current phone.



Applies to: Field

Specifies whether the target field value is required in a form. For example, the activityUser and project fields are required for an Activity.

public class Activity extends Record {

    @Indexed
    private Date activityDate;

    @Required
    private User activityUser;

    @Required
    private Project project;

}



Applies to: Class

Specifies the source database class for the target type. The annotation takes a value of an implementing class of com.psddev.dari.db.Database.

For example, you might have a large dataset that you do not want to be included in your default MySQL database, so you specify ElasticSearch storage:

@SourceDatabaseClass(com.psddev.dari.elasticsearch.ElasticsearchDatabase.class)
public static class Order extends Record {

}



Applies to: Class

Specifies the source database name for the target type. This annotation is an alternative to @SourceDatabaseClass. Instead of specifying the database class, you specify the name to match the database configuration as configured in the Tomcat context.xml (see Database configuration).

@SourceDatabaseName("OrderProcessing")
public static class Order extends Record {

}



Applies to: Field

Specifies the incremental step between the minimum and the maximum that the target field must match. The @Step annotation can only be used if the @Minimum and @Maximum annotations are also present.

Based on the following annotations, the rate field accepts a value range of 1 to 10 percent in half-percent increments.

public class Interest extends Record {

    private boolean dealerCharged;

    @Minimum(.01)
    @Maximum(.10)
    @Step(.005)
    private double rate;

}



Applies to: Class

Forces the type ID to the specified value instead of automatic generation of the ID. A type ID conflict with another class has no impact.

This annotation is typically used to facilitate code refactoring, allowing you to maintain existing type IDs for classes that you relocate to different packages. For example, the following snippet defines a class Article prior to refactoring.

import com.psddev.cms.db.Content;

public class Article extends Content {

    /* Fields, getters, setters */

}


Dari’s internal representation of an instantiated article is as follows:

{
  "name": "Article", 
  "internalName": "content.article.Article", 
  "_id": "a3489571-fd7e-3ca4-b850-61909ef172cd", 
  "_type": "982a8b2a-7600-3bb0-ae68-740f77cd85d3" 
}
  • Class’s name
  • Class’s internal name constructed as a standard fully qualified class name.
  • Unique ID of the object.
  • Unique ID of the article class.

As your project evolves, you decide to create a new content type Blog in a different package that is identical to the content type Article. Using the annotation @Recordable.TypeId you can force Dari to store the blog as an article.

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

@Recordable.TypeId("982a8b2a-7600-3bb0-ae68-740f77cd85d3") 
public class Blog extends Content  {

}
  • Applies the annotation @Recordable.TypeId, assigning the article’s type ID to the blog type.

Dari’s internal representation for an instantiated blog reflects the internal name and type ID for Article.

{
  "name": "Article", 
  "internalName": "content.blog.Article", 
  "_id": "daba47fb-df6e-3e05-bfa3-d7d943cc346d", 
  "_type": "982a8b2a-7600-3bb0-ae68-740f77cd85d3" 
}
  • Class’s name. Because of the annotation, the class’s name is Article even if the class declaration is Blog.
  • Class’s internal name constructed as a standard fully qualified class name.
  • Unique ID of the object.
  • Unique ID of the article class from which the blog class is duplicated.


See also:


Applies to: Class

Specifies an array of processor classes to run after the type is initialized. When an instance of the target class is created, your implementation of ObjectType.PostProcessor executes.


Applies to: Field

Specifies the valid class types for the target field value. In the example below, only Image and Video types, which derive from Media, can be added to the list of items.

public class Gallery extends Content {

    @Types( {Image.class, Video.class} )
    private List<Media> items;

}



Applies to: Field

Specifies the class types to be excluded for the target field value. In the example below, all types that derive from Promotable can be set on the item field except for Image.

public class Promo extends Content {

    @TypesExclude( {Image.class} )
    private Promotable item;

}



Applies to: Field

Specifies the valid values for the target field. In the example below, only one of the listed colors can be set on the teamColor field.

public class Team extends Content {

    private String teamName;

    @Values({"red", "blue", "yellow", "green"})
    private String teamColor;

}



Applies to: Field

Allows you to specify a predicate for a reference field of a Record-derived type. This annotation is used for data validation and dynamic lookups in a Brightspot selection field.

Data validation

For data validation, the predicate is evaluated against a value. If the value does not match the predicate, a validation error is thrown.

In this example, the @Where annotation enforces the requirement that new articles be created by authors with an expertise of Exploration.

The Author class includes an expertise field for setting a writer’s experience focus.

import com.psddev.cms.db.Content;

public class Author extends Content {

    @Indexed
    private String name;

    @Indexed
    private String expertise;
   
    /* Getters and setters */

}

The Article class applies the @Where annotation on the author field.

import com.psddev.cms.db.Content;
import content.Image;
import content.Author;

public class Article extends Content {
    private String headline;
    private Image leadImage;
    private String body;

    @Where("expertise = 'Exploration'")
    private Author author;
    /* Getters and Setters */

    protected void onValidate() { 
        getState().addError(getState().getField("author"), "Select an author with Exploration expertise");
    }
}
  • Implements the onValidate callback method in the event that a ValidationException is thrown because an invalid value is set on the author field.


If an author is selected without Exploration expertise, the callback sends an extended message for Brightspot to display in the content edit form. (For information about validation time and when it occurs, see Save life cycle.)

Validation exception Validation exception


When you use the @Where annotation, Brightspot automatically filters results in the content picker based on the predicate set on the annotation. Continuing with the previous example, when a user opens the content picker for the author field, the search finds the authors with Exploration expertise.

Validation exception Validation exception


Dynamic lookups


You can use the @Where annotation to filter results based on values returned by a method, allowing for dynamic lookups in a Brightspot selection field.

The following snippet implements a dynamic lookup for articles in a collection field.

@Where("tags = ?/getTags")
private Set<Article> relatedStories;

In Brightspot, the @Where annotation will filter the collection of articles based on the tags field, another collection field with values generated by the getTags method. The question mark ? represents the current object (similar to Java’s keyword this), so the method getTags is a member of the current object.

The following example gives additional context to dynamically filtering a collection of articles based on tags, used to relate like articles. Articles are related when they are set with one or more of the same tags, represented by a Tag object.

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

public class Article extends Content {

    @Recordable.Required 
    private String headline;

    private Image leadImage;

    @ToolUi.RichText
    private String body;

    @Indexed 
    private Set<Tag> tags;

    @Where("tags = ?/getTags") 
    private Set<Article> relatedStories;

    /* Getters and setters */

    @ToolUi.Hidden 
    @Ignored(false) 
    public Set<Tag> getTags() { 
        if (tags != null) {
            return tags; 
        }
        return new HashSet(); 
    }
}
  • Declares five fields for the content type Article: headline, lead image, body, tags, and a selection field for a related story.
  • Applies an @Indexed annotation, allowing Dari to search for all the tags associated with a given article.
  • Applies a dynamic @Where annotation. The annotation populates the relatedStories selection field with articles whose tags are in the set returned by the method getTags.
  • Applies the @ToolUi.Hidden annotation to the method getTags. The annotation prevents the method’s results from appearing in the content edit form as a separate field.
  • Applies the @Ignored(false) annotation to the method getTags. The annotation ensures the method is run when the user clicks on the selection field.
  • Declares the method’s return type, a set of Tag objects.
  • Returns a set of tags in the current article (if any). Brightspot uses this list in the @Where annotation to populate the content picker
  • Returns an empty set if the current article has no tags.

Content picker with where annotation Content picker with where annotation



Applies to: Class

Specifies the application path for the target servlet. The purpose of the annotation is to group servlets together under a particular path segment. The annotation is the equivalent of the Java @WebServlet annotation, but includes an optional application element to organize servlets into logical groups.

In the following example, the annotation specifies the path of the servlet using the required and optional elements. The optional application element is the group path for logically related servlets. The required value element specifies the rest of the URL path where the target servlet is located.

@RoutingFilter.Path(application = "auth", value = "/admin/employees")
public class AuthenticateUsers extends PageServlet {

}


If the routing filter is set in Tomcat’s context.xml, that setting overrides the application path specified in the annotation. For example, the following application path in context.xml would be used instead of the value specified in the above annotation:

<Environment name="dari/routingFilter/applicationPath/auth" type="java.lang.String" value="/admin/users" />



Applies to: Class

Specifies the fields in the target class for which you want to track updates. Dari provides an API that indicates whether instances of a class are updated within a specified period of time. This includes new instances that are created as well as existing instances that are changed.

In the following snippet, the @UpdateTrackable.Names annotation specifies tracking of the activityUser and project fields in the Activity class.

@UpdateTrackable.Names( {"activityUser", "project"})
public class Activity extends Record {

    @Indexed
    private User activityUser;

    @Indexed
    private Project project;

    private Date activityDate;
    private String activityType;

}


The UpdateTrackable.Static.isUpdated method indicates whether instances of a specified class were updated within a specified time period. In the following example, a boolean is returned that indicates if activityUser or project values were created or changed for any Activity instances over the last hour.

public class Code {

    public static Object main() throws Throwable {
        long time = 3600000;
        String[] type = {"Activity"};
        return (UpdateTrackable.Static.isUpdated(type[0], time));
    }

}

Previous Topic
Object labels
Next Topic
Modifications
Was this topic helpful?
Thanks for your feedback.
Our robust, flexible Design System provides hundreds of pre-built components you can use to build the presentation layer of your dreams.

Asset types
Module types
Page types
Brightspot is packaged with content types that get you up and running in a matter of days, including assets, modules and landing pages.

Content types
Modules
Landing pages
Everything you need to know when creating, managing, and administering content within Brightspot CMS.

Dashboards
Publishing
Workflows
Admin configurations
A guide for installing, supporting, extending, modifying and administering code on the Brightspot platform.

Field types
Content modeling
Rich-text elements
Images
A guide to configuring Brightspot's library of integrations, including pre-built options and developer-configured extensions.

Google Analytics
Shopify
Apple News