Entity Examples
Overview
Concrete examples of each entity classification type demonstrating security boundaries and access patterns.
Org-Scoped Entity: Event
Events belong to an organization and inherit organization-level security.
package com.example.domain;
import javax.persistence.*;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.Set;
/**
* Org-scoped entity example: Event belongs to an organization.
*/
@Entity
@Table(name = "event")
public class Event implements OrganisationScoped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "org_id", nullable = false)
private Long orgId;
@Column(nullable = false, length = 200)
private String name;
@Column(name = "start_date", nullable = false)
private LocalDate startDate;
@Column(name = "end_date")
private LocalDate endDate;
@Column(columnDefinition = "TEXT")
private String description;
@OneToMany(mappedBy = "event", cascade = CascadeType.ALL)
private Set<Race> races = new HashSet<>();
// OrganisationScoped implementation
@Override
public Long getOrgId() {
return orgId;
}
@Override
public void setOrgId(Long orgId) {
this.orgId = orgId;
}
// Standard getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDate getStartDate() {
return startDate;
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public LocalDate getEndDate() {
return endDate;
}
public void setEndDate(LocalDate endDate) {
this.endDate = endDate;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Set<Race> getRaces() {
return races;
}
public void setRaces(Set<Race> races) {
this.races = races;
}
}
Person-Scoped Entity: PersonProfile
Person profiles are scoped to individual users and protected by person-level security.
package com.example.domain;
import javax.persistence.*;
/**
* Person-scoped entity example: Contains personal data for a specific person.
*/
@Entity
@Table(name = "person_profile")
public class PersonProfile implements PersonScoped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "person_id", nullable = false)
private Long personId;
@Column(length = 200)
private String emergencyContactName;
@Column(length = 50)
private String emergencyContactPhone;
@Column(columnDefinition = "TEXT")
private String medicalInfo;
@Column(columnDefinition = "TEXT")
private String dietaryRequirements;
@Column(length = 100)
private String tshirtSize;
// PersonScoped implementation
@Override
public Long getPersonId() {
return personId;
}
@Override
public void setPersonId(Long personId) {
this.personId = personId;
}
// Standard getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmergencyContactName() {
return emergencyContactName;
}
public void setEmergencyContactName(String emergencyContactName) {
this.emergencyContactName = emergencyContactName;
}
public String getEmergencyContactPhone() {
return emergencyContactPhone;
}
public void setEmergencyContactPhone(String emergencyContactPhone) {
this.emergencyContactPhone = emergencyContactPhone;
}
public String getMedicalInfo() {
return medicalInfo;
}
public void setMedicalInfo(String medicalInfo) {
this.medicalInfo = medicalInfo;
}
public String getDietaryRequirements() {
return dietaryRequirements;
}
public void setDietaryRequirements(String dietaryRequirements) {
this.dietaryRequirements = dietaryRequirements;
}
public String getTshirtSize() {
return tshirtSize;
}
public void setTshirtSize(String tshirtSize) {
this.tshirtSize = tshirtSize;
}
}
Composite-Scoped Entity: EventEntry
Event entries implement both organization and person scoping interfaces. Each dimension is checked independently - access requires authorization in both the organization dimension AND the person dimension.
package com.example.domain;
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.Instant;
/**
* Composite-scoped entity example: Links a person to an event.
* Implements both OrganisationScoped and PersonScoped interfaces.
* Each security dimension is checked independently - access requires
* authorization in BOTH the organization dimension AND the person dimension.
*/
@Entity
@Table(name = "event_entry")
public class EventEntry implements OrganisationScoped, PersonScoped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(optional = false)
@JoinColumn(name = "event_id", nullable = false)
private Event event;
@Column(name = "person_id", nullable = false)
private Long personId;
@Column(name = "entry_date", nullable = false)
private Instant entryDate;
@Column(name = "amount_paid", precision = 10, scale = 2)
private BigDecimal amountPaid;
@Enumerated(EnumType.STRING)
@Column(length = 20)
private EntryStatus status;
@Column(columnDefinition = "TEXT")
private String notes;
// OrganisationScoped implementation - orgId is transitive via event
@Override
public Long getOrgId() {
return event != null ? event.getOrgId() : null;
}
@Override
public void setOrgId(Long orgId) {
throw new UnsupportedOperationException("OrgId is derived from event");
}
// PersonScoped implementation - personId is direct
@Override
public Long getPersonId() {
return personId;
}
@Override
public void setPersonId(Long personId) {
this.personId = personId;
}
// Standard getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Event getEvent() {
return event;
}
public void setEvent(Event event) {
this.event = event;
}
public Instant getEntryDate() {
return entryDate;
}
public void setEntryDate(Instant entryDate) {
this.entryDate = entryDate;
}
public BigDecimal getAmountPaid() {
return amountPaid;
}
public void setAmountPaid(BigDecimal amountPaid) {
this.amountPaid = amountPaid;
}
public EntryStatus getStatus() {
return status;
}
public void setStatus(EntryStatus status) {
this.status = status;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
}
Transitive Org-Scoped: Race
Races inherit organization scope transitively through their parent Event.
package com.example.domain;
import javax.persistence.*;
/**
* Transitive org-scoped entity example: Race belongs to Event, inherits orgId.
*/
@Entity
@Table(name = "race")
public class Race {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(optional = false)
@JoinColumn(name = "event_id", nullable = false)
private Event event;
@Column(nullable = false, length = 100)
private String name;
@Column(nullable = false)
private Double distance;
@Column(length = 20)
private String distanceUnit;
@Column(columnDefinition = "TEXT")
private String description;
/**
* Helper method for security: get orgId transitively from event
*/
public Long getOrgId() {
return event != null ? event.getOrgId() : null;
}
// Standard getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Event getEvent() {
return event;
}
public void setEvent(Event event) {
this.event = event;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getDistance() {
return distance;
}
public void setDistance(Double distance) {
this.distance = distance;
}
public String getDistanceUnit() {
return distanceUnit;
}
public void setDistanceUnit(String distanceUnit) {
this.distanceUnit = distanceUnit;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
Next Steps
-
Implement Security Service
-
Create Query Services