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.
@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.
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.
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.
@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.
@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.
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:
- At deploy time, this annotation places the method in a list containing other methods with this annotation.
- 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
)
- On load of the content edit form (
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
:
- Create a new instance of
TeamMemberContactInformation
. - Populate
TeamMemberContactInformation
with the existingphone
andemail
. - Set the existing
phone
andemail
tonull
.
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 fieldsphone
andemail
. -
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 ofTeamMemberContactInformation
(if one doesn't already exist), and assign that instance'semail
to the currentemail
. -
If this object has a
phone
, create a new instance ofTeamMemberContactInformation
(if one doesn't already exist), and assign that instance'sphone
to the currentphone
.
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 isBlog
.
-
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 theauthor
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.)
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.
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 therelatedStories
selection field with articles whose tags are in the set returned by the methodgetTags
.
-
Applies the
@ToolUi.Hidden
annotation to the methodgetTags
. 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 methodgetTags
. 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.
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));
}
}