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