@PalmyraField#
com.palmyralabs.palmyra.base.annotations.PalmyraField
Attaches metadata to an attribute — column mapping, sort/search flags, validation, ACL, and pre-save transforms. Target: FIELD. Retention: RUNTIME.
Attributes#
| Attribute | Signature |
|---|---|
attribute |
String attribute() default "" — bean attribute name |
value |
String value() default "" — alias for attribute |
name |
String name() default "" — logical field name |
column |
String column() default "" — DB column |
title |
String title() default "" — display title |
displayOrder |
int displayOrder() default 0 |
primaryKey |
boolean primaryKey() default false |
keyField |
boolean keyField() default false — logical key field |
primaryRef |
String primaryRef() default "" |
parentRef |
String parentRef() default "" |
search |
boolean search() default false — searchable |
sort |
boolean sort() default false — sortable |
quickSearch |
boolean quickSearch() default false |
pattern |
String pattern() default "" — validation regex |
notNull |
boolean notNull() default false |
mandatory |
Mandatory mandatory() default Mandatory.NONE — operation-scoped required check (see Mandatory) |
active |
boolean active() default true |
virtual |
boolean virtual() default false — non-persistent |
aclMask |
Acl aclMask() default Acl.DEFAULT |
drop |
DropMode drop() default DropMode.NONE — strip the attribute in a given direction (see DropMode) |
preProcessor |
Class<? extends Function<Object,Object>> preProcessor() default processor.class — pre-save transform |
childMode |
ChildProcessMode childMode() default ChildProcessMode.MERGE |
Example#
public class User {
@PalmyraField(attribute = "id", column = "user_id", primaryKey = true)
private Long id;
@PalmyraField(attribute = "email", sort = true, search = true, quickSearch = true)
private String loginName;
@PalmyraField(attribute = "displayName", pattern = "^[A-Za-z ]{1,64}$", notNull = true)
private String name;
}Flattening with parentRef#
parentRef lets a DTO expose a scalar that’s actually sourced from a joined table. The client sees a flat JSON shape; the backend still does the join. The dotted path can live on either parentRef or attribute.
One hop — from an immediate parent#
// StockEntry → purchaseRef.purchaseDate
@PalmyraField(parentRef = "purchaseRef", attribute = "purchaseDate")
private LocalDate purchaseDate;Two hops — dotted parentRef#
// StockEntry → purchaseRef → supplier → name
@PalmyraField(parentRef = "purchaseRef.supplier", attribute = "name")
private String supplier;Two hops — dotted attribute#
Same result as the previous form — the dot is just on the other side:
// PurchasePaymentLineItem → purchase → supplier → name
@PalmyraField(parentRef = "purchase", attribute = "supplier.name")
private String vendor;
// PurchasePaymentLineItem → purchase → paymentStatus → code
@PalmyraField(parentRef = "purchase", attribute = "paymentStatus.code")
private String statusCode;Real-world examples from the clinic sample live in StockEntryModel and PurchasePaymentLineItemModel. See the schema tutorial for the full walk-through.
Mandatory#
com.palmyralabs.palmyra.base.format.Mandatory
Controls when the attribute is required. The framework evaluates the rule against the current CRUD action — a field can be required on create but optional on update, or required on every write but ignored for deletes.
| Constant | Required on | ACL rights mask |
|---|---|---|
NONE |
never (default) | 0 |
ALL |
every operation | ACLRights.ALL |
CREATE |
create (insert) | ACLRights.READ_CREATE |
UPDATE |
update | ACLRights.READ_UPDATE |
SAVE |
create or update through SaveHandler (upsert) |
ACLRights.READ_SAVE |
DELETE |
delete | ACLRights.READ | ACLRights.DELETE |
The mandatory check runs inside the PreProcessor pipeline before the handler’s lifecycle hooks fire. A missing required value throws a validation error and the operation aborts — so your preCreate / preUpdate / preSave hooks never see a half-constructed tuple.
Example#
public class User {
// Required on every operation — identity field
@PalmyraField(attribute = "loginName", mandatory = Mandatory.ALL)
private String loginName;
// Required on create, optional on subsequent updates
@PalmyraField(attribute = "password", mandatory = Mandatory.CREATE)
private String password;
// Required whenever the row is being written via SaveHandler (upsert)
@PalmyraField(attribute = "tenantId", mandatory = Mandatory.SAVE)
private Long tenantId;
}Pair Mandatory with notNull for belt-and-braces validation — notNull is a schema-level guarantee; mandatory picks the action it applies to.
DropMode#
com.palmyralabs.palmyra.base.format.DropMode
Controls when the attribute is stripped from the payload as it crosses the serialization boundary. Use it to hide write-only or read-only columns without defining a separate DTO.
| Constant | Wire value | Stripped on |
|---|---|---|
NONE |
"none" |
Never — default |
INCOMING |
"incoming" |
Requests — the client cannot write this attribute (silently ignored if supplied) |
OUTGOING |
"outgoing" |
Responses — never serialized back to the client |
NULL |
"ignore-null" |
Responses — skipped only when the value is null |
Example#
public class User {
// Server-managed — the client can read it but never set it
@PalmyraField(attribute = "createdAt", drop = DropMode.INCOMING)
private Instant createdAt;
// Persisted + validated, but never echoed back in a response
@PalmyraField(attribute = "passwordHash", drop = DropMode.OUTGOING)
private String passwordHash;
// Optional — keep the wire payload slim when it's absent
@PalmyraField(attribute = "secondaryEmail", drop = DropMode.NULL)
private String secondaryEmail;
}INCOMING is the natural fit for audit columns (createdBy, updatedAt, tenantId) that your preCreate / preUpdate hook populates — clients can’t overwrite them, and the server stays authoritative. OUTGOING is how you protect sensitive columns (hashes, tokens, internal flags) without maintaining a shadow DTO.