This tutorial assumes you have at least skimmed the Domain model part of the Codion manual.
Each section of the tutorial below is based on a single table and has the following subsections:
- SQL
-
The underlying table DDL.
- Domain
-
The domain model specification for an entity based on the table.
- Model
-
The application model components associated with the entity (if any).
- UI
-
The application UI components associated with the entity.
Domain model
The domain model is created by extending the DefaultDomain class and defining a DomainType constant identifying the domain model.
In the constructor we call methods adding a single Entity definition to the domain model. The Domain subsections below continue the Petclinic class.
public final class Petclinic extends DefaultDomain {
public static final DomainType DOMAIN = domainType("Petclinic");
public Petclinic() {
super(DOMAIN);
vet();
specialty();
vetSpecialty();
petType();
owner();
pet();
visit();
}
Display full Petclinic class
/*
* This file is part of Codion Petclinic Demo.
*
* Codion Petclinic Demo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Codion Petclinic Demo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Codion Petclinic Demo. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (c) 2004 - 2023, Björn Darri Sigurðsson.
*/
package is.codion.framework.demos.petclinic.domain;
import is.codion.framework.demos.petclinic.domain.Petclinic.Owner.PhoneType;
import is.codion.framework.domain.DefaultDomain;
import is.codion.framework.domain.DomainType;
import is.codion.framework.domain.entity.Entity;
import is.codion.framework.domain.entity.EntityType;
import is.codion.framework.domain.entity.OrderBy;
import is.codion.framework.domain.entity.StringFactory;
import is.codion.framework.domain.entity.attribute.Column;
import is.codion.framework.domain.entity.attribute.Column.Converter;
import is.codion.framework.domain.entity.attribute.ForeignKey;
import java.io.Serializable;
import java.sql.Statement;
import java.time.LocalDate;
import java.util.function.Predicate;
import static is.codion.framework.domain.DomainType.domainType;
import static is.codion.framework.domain.entity.KeyGenerator.identity;
import static is.codion.framework.domain.entity.OrderBy.ascending;
public final class Petclinic extends DefaultDomain {
public static final DomainType DOMAIN = domainType("Petclinic");
public Petclinic() {
super(DOMAIN);
vet();
specialty();
vetSpecialty();
petType();
owner();
pet();
visit();
}
public interface Vet {
EntityType TYPE = DOMAIN.entityType("petclinic.vet");
Column<Integer> ID = TYPE.integerColumn("id");
Column<String> FIRST_NAME = TYPE.stringColumn("first_name");
Column<String> LAST_NAME = TYPE.stringColumn("last_name");
}
private void vet() {
add(Vet.TYPE.define(
Vet.ID.define()
.primaryKey(),
Vet.FIRST_NAME.define()
.column()
.caption("First name")
.searchable(true)
.maximumLength(30)
.nullable(false),
Vet.LAST_NAME.define()
.column()
.caption("Last name")
.searchable(true)
.maximumLength(30)
.nullable(false))
.keyGenerator(identity())
.caption("Vets")
.stringFactory(StringFactory.builder()
.value(Vet.LAST_NAME)
.text(", ")
.value(Vet.FIRST_NAME)
.build())
.orderBy(ascending(Vet.LAST_NAME, Vet.FIRST_NAME))
.smallDataset(true));
}
public interface Specialty {
EntityType TYPE = DOMAIN.entityType("petclinic.specialty");
Column<Integer> ID = TYPE.integerColumn("id");
Column<String> NAME = TYPE.stringColumn("name");
}
private void specialty() {
add(Specialty.TYPE.define(
Specialty.ID.define()
.primaryKey(),
Specialty.NAME.define()
.column()
.caption("Name")
.searchable(true)
.maximumLength(80)
.nullable(false))
.keyGenerator(identity())
.caption("Specialties")
.stringFactory(Specialty.NAME)
.smallDataset(true));
}
public interface VetSpecialty {
EntityType TYPE = DOMAIN.entityType("petclinic.vet_specialty");
Column<Integer> VET = TYPE.integerColumn("vet");
Column<Integer> SPECIALTY = TYPE.integerColumn("specialty");
ForeignKey VET_FK = TYPE.foreignKey("vet_fk", VET, Vet.ID);
ForeignKey SPECIALTY_FK = TYPE.foreignKey("specialty_fk", SPECIALTY, Specialty.ID);
final class Exists implements Predicate<Entity>, Serializable {
private static final long serialVersionUID = 1;
@Override
public boolean test(Entity entity) {
return entity.originalPrimaryKey().isNotNull();
}
}
}
private void vetSpecialty() {
add(VetSpecialty.TYPE.define(
VetSpecialty.VET.define()
.primaryKey(0)
.updatable(true),
VetSpecialty.SPECIALTY.define()
.primaryKey(1)
.updatable(true),
VetSpecialty.VET_FK.define()
.foreignKey()
.caption("Vet"),
VetSpecialty.SPECIALTY_FK.define()
.foreignKey()
.caption("Specialty"))
.caption("Vet specialties")
.stringFactory(StringFactory.builder()
.value(VetSpecialty.VET_FK)
.text(" - ")
.value(VetSpecialty.SPECIALTY_FK)
.build())
.exists(new VetSpecialty.Exists()));
}
public interface PetType {
EntityType TYPE = DOMAIN.entityType("petclinic.pet_type");
Column<Integer> ID = TYPE.integerColumn("id");
Column<String> NAME = TYPE.stringColumn("name");
}
private void petType() {
add(PetType.TYPE.define(
PetType.ID.define()
.primaryKey(),
PetType.NAME.define()
.column()
.caption("Name")
.searchable(true)
.maximumLength(80)
.nullable(false))
.keyGenerator(identity())
.caption("Pet types")
.stringFactory(PetType.NAME)
.orderBy(ascending(PetType.NAME))
.smallDataset(true));
}
public interface Owner {
EntityType TYPE = DOMAIN.entityType("petclinic.owner");
Column<Integer> ID = TYPE.integerColumn("id");
Column<String> FIRST_NAME = TYPE.stringColumn("first_name");
Column<String> LAST_NAME = TYPE.stringColumn("last_name");
Column<String> ADDRESS = TYPE.stringColumn("address");
Column<String> CITY = TYPE.stringColumn("city");
Column<String> TELEPHONE = TYPE.stringColumn("telephone");
Column<PhoneType> PHONE_TYPE = TYPE.column("phone_type", PhoneType.class);
enum PhoneType {
MOBILE, HOME, WORK
}
}
private void owner() {
add(Owner.TYPE.define(
Owner.ID.define()
.primaryKey(),
Owner.FIRST_NAME.define()
.column()
.caption("First name")
.searchable(true)
.maximumLength(30)
.nullable(false),
Owner.LAST_NAME.define()
.column()
.caption("Last name")
.searchable(true)
.maximumLength(30)
.nullable(false),
Owner.ADDRESS.define()
.column()
.caption("Address")
.maximumLength(255),
Owner.CITY.define()
.column()
.caption("City")
.maximumLength(80),
Owner.TELEPHONE.define()
.column()
.caption("Telephone")
.maximumLength(20),
Owner.PHONE_TYPE.define()
.column()
.caption("Phone type")
.columnClass(String.class, new PhoneTypeConverter()))
.keyGenerator(identity())
.caption("Owners")
.stringFactory(StringFactory.builder()
.value(Owner.LAST_NAME)
.text(", ")
.value(Owner.FIRST_NAME)
.build())
.orderBy(ascending(Owner.LAST_NAME, Owner.FIRST_NAME)));
}
private static final class PhoneTypeConverter implements Converter<PhoneType, String> {
@Override
public String toColumnValue(PhoneType value, Statement statement) {
return value.name();
}
@Override
public PhoneType fromColumnValue(String columnValue) {
return PhoneType.valueOf(columnValue);
}
}
public interface Pet {
EntityType TYPE = DOMAIN.entityType("petclinic.pet");
Column<Integer> ID = TYPE.integerColumn("id");
Column<String> NAME = TYPE.stringColumn("name");
Column<LocalDate> BIRTH_DATE = TYPE.localDateColumn("birth_date");
Column<Integer> PET_TYPE_ID = TYPE.integerColumn("type_id");
Column<Integer> OWNER_ID = TYPE.integerColumn("owner_id");
ForeignKey PET_TYPE_FK = TYPE.foreignKey("type_fk", PET_TYPE_ID, PetType.ID);
ForeignKey OWNER_FK = TYPE.foreignKey("owner_fk", OWNER_ID, Owner.ID);
}
private void pet() {
add(Pet.TYPE.define(
Pet.ID.define()
.primaryKey(),
Pet.NAME.define()
.column()
.caption("Name")
.searchable(true)
.maximumLength(30)
.nullable(false),
Pet.BIRTH_DATE.define()
.column()
.caption("Birth date")
.nullable(false),
Pet.PET_TYPE_ID.define()
.column()
.nullable(false),
Pet.PET_TYPE_FK.define()
.foreignKey()
.caption("Pet type"),
Pet.OWNER_ID.define()
.column()
.nullable(false),
Pet.OWNER_FK.define()
.foreignKey()
.caption("Owner"))
.keyGenerator(identity())
.caption("Pets")
.stringFactory(Pet.NAME)
.orderBy(ascending(Pet.NAME)));
}
public interface Visit {
EntityType TYPE = DOMAIN.entityType("petclinic.visit");
Column<Integer> ID = TYPE.integerColumn("id");
Column<Integer> PET_ID = TYPE.integerColumn("pet_id");
Column<LocalDate> VISIT_DATE = TYPE.localDateColumn("visit_date");
Column<String> DESCRIPTION = TYPE.stringColumn("description");
ForeignKey PET_FK = TYPE.foreignKey("pet_fk", PET_ID, Pet.ID);
}
private void visit() {
add(Visit.TYPE.define(
Visit.ID.define()
.primaryKey(),
Visit.PET_ID.define()
.column()
.nullable(false),
Visit.PET_FK.define()
.foreignKey()
.caption("Pet"),
Visit.VISIT_DATE.define()
.column()
.caption("Date")
.nullable(false),
Visit.DESCRIPTION.define()
.column()
.caption("Description")
.maximumLength(255))
.keyGenerator(identity())
.orderBy(OrderBy.builder()
.ascending(Visit.PET_ID)
.descending(Visit.VISIT_DATE)
.build())
.caption("Visits"));
}
}
Owners
![owners](images/owners.png)
Owner
SQL
CREATE TABLE petclinic.owner (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
first_name VARCHAR(30) NOT NULL,
last_name VARCHAR(30) NOT NULL,
address VARCHAR(255),
city VARCHAR(80),
telephone VARCHAR(20),
phone_type VARCHAR(20) NOT NULL
);
Domain
API
We start by defining an Owner interface and defining constants for the table and its columns.
The EntityType identifies this entity and the Attributes identify the columns and their types. We define a PhoneType enum, which we use as a custom type for the attribute based on the phone_type column.
public interface Owner {
EntityType TYPE = DOMAIN.entityType("petclinic.owner");
Column<Integer> ID = TYPE.integerColumn("id");
Column<String> FIRST_NAME = TYPE.stringColumn("first_name");
Column<String> LAST_NAME = TYPE.stringColumn("last_name");
Column<String> ADDRESS = TYPE.stringColumn("address");
Column<String> CITY = TYPE.stringColumn("city");
Column<String> TELEPHONE = TYPE.stringColumn("telephone");
Column<PhoneType> PHONE_TYPE = TYPE.column("phone_type", PhoneType.class);
enum PhoneType {
MOBILE, HOME, WORK
}
}
Implementation
We then define an entity based on the attributes, wrapping each attribute in a property, allowing us to configure presentation and persistance.
private void owner() {
add(Owner.TYPE.define(
Owner.ID.define()
.primaryKey(),
Owner.FIRST_NAME.define()
.column()
.caption("First name")
.searchable(true)
.maximumLength(30)
.nullable(false),
Owner.LAST_NAME.define()
.column()
.caption("Last name")
.searchable(true)
.maximumLength(30)
.nullable(false),
Owner.ADDRESS.define()
.column()
.caption("Address")
.maximumLength(255),
Owner.CITY.define()
.column()
.caption("City")
.maximumLength(80),
Owner.TELEPHONE.define()
.column()
.caption("Telephone")
.maximumLength(20),
Owner.PHONE_TYPE.define()
.column()
.caption("Phone type")
.columnClass(String.class, new PhoneTypeConverter()))
.keyGenerator(identity())
.caption("Owners")
.stringFactory(StringFactory.builder()
.value(Owner.LAST_NAME)
.text(", ")
.value(Owner.FIRST_NAME)
.build())
.orderBy(ascending(Owner.LAST_NAME, Owner.FIRST_NAME)));
}
private static final class PhoneTypeConverter implements Converter<PhoneType, String> {
@Override
public String toColumnValue(PhoneType value, Statement statement) {
return value.name();
}
@Override
public PhoneType fromColumnValue(String columnValue) {
return PhoneType.valueOf(columnValue);
}
}
UI
We extend the EntityEditPanel class and implement the initializeUI method to provide a user interface for editing Owner instances.
-
Set the initial focus attribute, which specifies which input field should receive the focus when the panel is cleared or initialized.
-
Create input fields for the attributes.
-
Set a layout and add the input fields on the panel.
package is.codion.framework.demos.petclinic.ui;
import is.codion.framework.demos.petclinic.domain.Petclinic.Owner;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;
import static is.codion.swing.common.ui.layout.Layouts.gridLayout;
public final class OwnerEditPanel extends EntityEditPanel {
public OwnerEditPanel(SwingEntityEditModel editModel) {
super(editModel);
}
@Override
protected void initializeUI() {
initialFocusAttribute().set(Owner.FIRST_NAME);
createTextField(Owner.FIRST_NAME);
createTextField(Owner.LAST_NAME);
createTextField(Owner.ADDRESS);
createTextField(Owner.CITY);
createTextField(Owner.TELEPHONE);
createComboBox(Owner.PHONE_TYPE);
setLayout(gridLayout(3, 2));
addInputPanel(Owner.FIRST_NAME);
addInputPanel(Owner.LAST_NAME);
addInputPanel(Owner.ADDRESS);
addInputPanel(Owner.CITY);
addInputPanel(Owner.TELEPHONE);
addInputPanel(Owner.PHONE_TYPE);
}
}
- NOTE
-
The create… methods return a ComponentBuilder instance, providing a way to configure the input fields.
Pet
SQL
CREATE TABLE petclinic.pet (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(30),
birth_date DATE,
type_id INTEGER NOT NULL,
owner_id INTEGER NOT NULL,
CONSTRAINT fk_pet_owners FOREIGN KEY (owner_id)
REFERENCES owner (id),
CONSTRAINT fk_pet_pet_type FOREIGN KEY (type_id)
REFERENCES pet_type (id)
);
Domain
API
public interface Pet {
EntityType TYPE = DOMAIN.entityType("petclinic.pet");
Column<Integer> ID = TYPE.integerColumn("id");
Column<String> NAME = TYPE.stringColumn("name");
Column<LocalDate> BIRTH_DATE = TYPE.localDateColumn("birth_date");
Column<Integer> PET_TYPE_ID = TYPE.integerColumn("type_id");
Column<Integer> OWNER_ID = TYPE.integerColumn("owner_id");
ForeignKey PET_TYPE_FK = TYPE.foreignKey("type_fk", PET_TYPE_ID, PetType.ID);
ForeignKey OWNER_FK = TYPE.foreignKey("owner_fk", OWNER_ID, Owner.ID);
}
Implementation
private void pet() {
add(Pet.TYPE.define(
Pet.ID.define()
.primaryKey(),
Pet.NAME.define()
.column()
.caption("Name")
.searchable(true)
.maximumLength(30)
.nullable(false),
Pet.BIRTH_DATE.define()
.column()
.caption("Birth date")
.nullable(false),
Pet.PET_TYPE_ID.define()
.column()
.nullable(false),
Pet.PET_TYPE_FK.define()
.foreignKey()
.caption("Pet type"),
Pet.OWNER_ID.define()
.column()
.nullable(false),
Pet.OWNER_FK.define()
.foreignKey()
.caption("Owner"))
.keyGenerator(identity())
.caption("Pets")
.stringFactory(Pet.NAME)
.orderBy(ascending(Pet.NAME)));
}
UI
package is.codion.framework.demos.petclinic.ui;
import is.codion.framework.demos.petclinic.domain.Petclinic.Pet;
import is.codion.framework.demos.petclinic.domain.Petclinic.PetType;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;
import static is.codion.swing.common.ui.layout.Layouts.gridLayout;
public final class PetEditPanel extends EntityEditPanel {
public PetEditPanel(SwingEntityEditModel editModel) {
super(editModel);
}
@Override
protected void initializeUI() {
initialFocusAttribute().set(Pet.NAME);
createForeignKeyComboBox(Pet.OWNER_FK);
createTextField(Pet.NAME);
createTemporalFieldPanel(Pet.BIRTH_DATE);
createForeignKeyComboBoxPanel(Pet.PET_TYPE_FK, this::createPetTypeEditPanel)
.add(true);
setLayout(gridLayout(2, 2));
addInputPanel(Pet.OWNER_FK);
addInputPanel(Pet.NAME);
addInputPanel(Pet.BIRTH_DATE);
addInputPanel(Pet.PET_TYPE_FK);
}
private PetTypeEditPanel createPetTypeEditPanel() {
return new PetTypeEditPanel(new SwingEntityEditModel(PetType.TYPE, editModel().connectionProvider()));
}
}
Visit
SQL
CREATE TABLE petclinic.visit (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
pet_id INTEGER NOT NULL,
visit_date DATE NOT NULL,
description VARCHAR(255),
CONSTRAINT fk_visit_pets FOREIGN KEY (pet_id)
REFERENCES pet (id)
);
Domain
API
public interface Visit {
EntityType TYPE = DOMAIN.entityType("petclinic.visit");
Column<Integer> ID = TYPE.integerColumn("id");
Column<Integer> PET_ID = TYPE.integerColumn("pet_id");
Column<LocalDate> VISIT_DATE = TYPE.localDateColumn("visit_date");
Column<String> DESCRIPTION = TYPE.stringColumn("description");
ForeignKey PET_FK = TYPE.foreignKey("pet_fk", PET_ID, Pet.ID);
}
Implementation
private void visit() {
add(Visit.TYPE.define(
Visit.ID.define()
.primaryKey(),
Visit.PET_ID.define()
.column()
.nullable(false),
Visit.PET_FK.define()
.foreignKey()
.caption("Pet"),
Visit.VISIT_DATE.define()
.column()
.caption("Date")
.nullable(false),
Visit.DESCRIPTION.define()
.column()
.caption("Description")
.maximumLength(255))
.keyGenerator(identity())
.orderBy(OrderBy.builder()
.ascending(Visit.PET_ID)
.descending(Visit.VISIT_DATE)
.build())
.caption("Visits"));
}
UI
package is.codion.framework.demos.petclinic.ui;
import is.codion.framework.demos.petclinic.domain.Petclinic.Visit;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import java.awt.BorderLayout;
import static is.codion.swing.common.ui.component.Components.gridLayoutPanel;
import static is.codion.swing.common.ui.layout.Layouts.borderLayout;
public final class VisitEditPanel extends EntityEditPanel {
public VisitEditPanel(SwingEntityEditModel editModel) {
super(editModel);
}
@Override
protected void initializeUI() {
initialFocusAttribute().set(Visit.PET_FK);
createForeignKeyComboBox(Visit.PET_FK);
createTemporalFieldPanel(Visit.VISIT_DATE);
createTextArea(Visit.DESCRIPTION)
.rowsColumns(4, 20);
JPanel northPanel = gridLayoutPanel(1, 2)
.add(createInputPanel(Visit.PET_FK))
.add(createInputPanel(Visit.VISIT_DATE))
.build();
setLayout(borderLayout());
add(northPanel, BorderLayout.NORTH);
addInputPanel(Visit.DESCRIPTION, new JScrollPane(component(Visit.DESCRIPTION).get()), BorderLayout.CENTER);
}
}
Support tables
Pet Type
![pet types](images/pet_types.png)
SQL
CREATE TABLE petclinic.pet_type (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(80) NOT NULL
);
Domain
API
public interface PetType {
EntityType TYPE = DOMAIN.entityType("petclinic.pet_type");
Column<Integer> ID = TYPE.integerColumn("id");
Column<String> NAME = TYPE.stringColumn("name");
}
Implementation
private void petType() {
add(PetType.TYPE.define(
PetType.ID.define()
.primaryKey(),
PetType.NAME.define()
.column()
.caption("Name")
.searchable(true)
.maximumLength(80)
.nullable(false))
.keyGenerator(identity())
.caption("Pet types")
.stringFactory(PetType.NAME)
.orderBy(ascending(PetType.NAME))
.smallDataset(true));
}
UI
package is.codion.framework.demos.petclinic.ui;
import is.codion.framework.demos.petclinic.domain.Petclinic.PetType;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;
import static is.codion.swing.common.ui.layout.Layouts.gridLayout;
public final class PetTypeEditPanel extends EntityEditPanel {
public PetTypeEditPanel(SwingEntityEditModel editModel) {
super(editModel);
}
@Override
protected void initializeUI() {
initialFocusAttribute().set(PetType.NAME);
createTextField(PetType.NAME);
setLayout(gridLayout(1, 1));
addInputPanel(PetType.NAME);
}
}
Specialty
![specialties](images/specialties.png)
SQL
CREATE TABLE petclinic.specialty (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(80)
);
Domain
API
public interface Specialty {
EntityType TYPE = DOMAIN.entityType("petclinic.specialty");
Column<Integer> ID = TYPE.integerColumn("id");
Column<String> NAME = TYPE.stringColumn("name");
}
Implementation
private void specialty() {
add(Specialty.TYPE.define(
Specialty.ID.define()
.primaryKey(),
Specialty.NAME.define()
.column()
.caption("Name")
.searchable(true)
.maximumLength(80)
.nullable(false))
.keyGenerator(identity())
.caption("Specialties")
.stringFactory(Specialty.NAME)
.smallDataset(true));
}
UI
package is.codion.framework.demos.petclinic.ui;
import is.codion.framework.demos.petclinic.domain.Petclinic.Specialty;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;
import static is.codion.swing.common.ui.layout.Layouts.gridLayout;
public final class SpecialtyEditPanel extends EntityEditPanel {
public SpecialtyEditPanel(SwingEntityEditModel editModel) {
super(editModel);
}
@Override
protected void initializeUI() {
initialFocusAttribute().set(Specialty.NAME);
createTextField(Specialty.NAME);
setLayout(gridLayout(1, 1));
addInputPanel(Specialty.NAME);
}
}
Vet
![vets](images/vets.png)
SQL
CREATE TABLE petclinic.vet (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
first_name VARCHAR(30),
last_name VARCHAR(30)
);
Domain
API
public interface Vet {
EntityType TYPE = DOMAIN.entityType("petclinic.vet");
Column<Integer> ID = TYPE.integerColumn("id");
Column<String> FIRST_NAME = TYPE.stringColumn("first_name");
Column<String> LAST_NAME = TYPE.stringColumn("last_name");
}
Implementation
private void vet() {
add(Vet.TYPE.define(
Vet.ID.define()
.primaryKey(),
Vet.FIRST_NAME.define()
.column()
.caption("First name")
.searchable(true)
.maximumLength(30)
.nullable(false),
Vet.LAST_NAME.define()
.column()
.caption("Last name")
.searchable(true)
.maximumLength(30)
.nullable(false))
.keyGenerator(identity())
.caption("Vets")
.stringFactory(StringFactory.builder()
.value(Vet.LAST_NAME)
.text(", ")
.value(Vet.FIRST_NAME)
.build())
.orderBy(ascending(Vet.LAST_NAME, Vet.FIRST_NAME))
.smallDataset(true));
}
UI
package is.codion.framework.demos.petclinic.ui;
import is.codion.framework.demos.petclinic.domain.Petclinic.Vet;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;
import static is.codion.swing.common.ui.layout.Layouts.gridLayout;
public final class VetEditPanel extends EntityEditPanel {
public VetEditPanel(SwingEntityEditModel editModel) {
super(editModel);
}
@Override
protected void initializeUI() {
initialFocusAttribute().set(Vet.FIRST_NAME);
createTextField(Vet.FIRST_NAME);
createTextField(Vet.LAST_NAME);
setLayout(gridLayout(2, 1));
addInputPanel(Vet.FIRST_NAME);
addInputPanel(Vet.LAST_NAME);
}
}
Vet Specialty
SQL
CREATE TABLE petclinic.vet_specialty (
vet INTEGER NOT NULL,
specialty INTEGER NOT NULL,
CONSTRAINT fk_vet_specialty_vet FOREIGN KEY (vet)
REFERENCES petclinic.vet (id),
CONSTRAINT fk_vet_specialty_specialty FOREIGN KEY (specialty)
REFERENCES petclinic.specialty (id)
);
Domain
API
public interface VetSpecialty {
EntityType TYPE = DOMAIN.entityType("petclinic.vet_specialty");
Column<Integer> VET = TYPE.integerColumn("vet");
Column<Integer> SPECIALTY = TYPE.integerColumn("specialty");
ForeignKey VET_FK = TYPE.foreignKey("vet_fk", VET, Vet.ID);
ForeignKey SPECIALTY_FK = TYPE.foreignKey("specialty_fk", SPECIALTY, Specialty.ID);
final class Exists implements Predicate<Entity>, Serializable {
private static final long serialVersionUID = 1;
@Override
public boolean test(Entity entity) {
return entity.originalPrimaryKey().isNotNull();
}
}
}
Implementation
private void vetSpecialty() {
add(VetSpecialty.TYPE.define(
VetSpecialty.VET.define()
.primaryKey(0)
.updatable(true),
VetSpecialty.SPECIALTY.define()
.primaryKey(1)
.updatable(true),
VetSpecialty.VET_FK.define()
.foreignKey()
.caption("Vet"),
VetSpecialty.SPECIALTY_FK.define()
.foreignKey()
.caption("Specialty"))
.caption("Vet specialties")
.stringFactory(StringFactory.builder()
.value(VetSpecialty.VET_FK)
.text(" - ")
.value(VetSpecialty.SPECIALTY_FK)
.build())
.exists(new VetSpecialty.Exists()));
}
Model
package is.codion.framework.demos.petclinic.model;
import is.codion.common.db.exception.DatabaseException;
import is.codion.framework.db.EntityConnectionProvider;
import is.codion.framework.demos.petclinic.domain.Petclinic.VetSpecialty;
import is.codion.framework.domain.entity.Entity;
import is.codion.framework.domain.entity.exception.ValidationException;
import is.codion.swing.framework.model.SwingEntityEditModel;
import static is.codion.framework.db.EntityConnection.Count.where;
import static is.codion.framework.domain.entity.condition.Condition.and;
public final class VetSpecialtyEditModel extends SwingEntityEditModel {
public VetSpecialtyEditModel(EntityConnectionProvider connectionProvider) {
super(VetSpecialty.TYPE, connectionProvider);
initializeComboBoxModels(VetSpecialty.VET_FK, VetSpecialty.SPECIALTY_FK);
persist(VetSpecialty.VET_FK).set(false);
persist(VetSpecialty.SPECIALTY_FK).set(false);
}
@Override
public void validate(Entity entity) throws ValidationException {
super.validate(entity);
try {
int rowCount = connectionProvider().connection().count(where(and(
VetSpecialty.SPECIALTY.equalTo(entity.get(VetSpecialty.SPECIALTY)),
VetSpecialty.VET.equalTo(entity.get(VetSpecialty.VET)))));
if (rowCount > 0) {
throw new ValidationException(VetSpecialty.SPECIALTY_FK,
entity.get(VetSpecialty.SPECIALTY_FK), "Vet/specialty combination already exists");
}
}
catch (DatabaseException e) {
throw new RuntimeException(e);
}
}
}
UI
package is.codion.framework.demos.petclinic.ui;
import is.codion.framework.demos.petclinic.domain.Petclinic.Specialty;
import is.codion.framework.demos.petclinic.domain.Petclinic.VetSpecialty;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;
import static is.codion.swing.common.ui.layout.Layouts.gridLayout;
public final class VetSpecialtyEditPanel extends EntityEditPanel {
public VetSpecialtyEditPanel(SwingEntityEditModel editModel) {
super(editModel);
defaults().foreignKeyComboBoxPreferredWidth().set(200);
}
@Override
protected void initializeUI() {
initialFocusAttribute().set(VetSpecialty.VET_FK);
createForeignKeyComboBox(VetSpecialty.VET_FK);
createForeignKeyComboBoxPanel(VetSpecialty.SPECIALTY_FK, this::createSpecialtyEditPanel)
.add(true);
setLayout(gridLayout(2, 1));
addInputPanel(VetSpecialty.VET_FK);
addInputPanel(VetSpecialty.SPECIALTY_FK);
}
private SpecialtyEditPanel createSpecialtyEditPanel() {
return new SpecialtyEditPanel(new SwingEntityEditModel(Specialty.TYPE, editModel().connectionProvider()));
}
}
PetclinicAppModel
package is.codion.framework.demos.petclinic.model;
import is.codion.common.version.Version;
import is.codion.framework.db.EntityConnectionProvider;
import is.codion.framework.demos.petclinic.domain.Petclinic.Owner;
import is.codion.framework.demos.petclinic.domain.Petclinic.Pet;
import is.codion.framework.demos.petclinic.domain.Petclinic.Visit;
import is.codion.swing.framework.model.SwingEntityApplicationModel;
import is.codion.swing.framework.model.SwingEntityModel;
public final class PetclinicAppModel extends SwingEntityApplicationModel {
public static final Version VERSION = Version.parsePropertiesFile(PetclinicAppModel.class, "/version.properties");
public PetclinicAppModel(EntityConnectionProvider connectionProvider) {
super(connectionProvider, VERSION);
setupEntityModels(connectionProvider);
}
private void setupEntityModels(EntityConnectionProvider connectionProvider) {
SwingEntityModel ownersModel = new SwingEntityModel(Owner.TYPE, connectionProvider);
SwingEntityModel petsModel = new SwingEntityModel(Pet.TYPE, connectionProvider);
petsModel.editModel().initializeComboBoxModels(Pet.OWNER_FK, Pet.PET_TYPE_FK);
SwingEntityModel visitModel = new SwingEntityModel(Visit.TYPE, connectionProvider);
visitModel.editModel().initializeComboBoxModels(Visit.PET_FK);
ownersModel.addDetailModel(petsModel);
petsModel.addDetailModel(visitModel);
ownersModel.tableModel().refresh();
addEntityModel(ownersModel);
}
}
PetclinicAppPanel
package is.codion.framework.demos.petclinic.ui;
import is.codion.common.model.CancelException;
import is.codion.common.user.User;
import is.codion.framework.demos.petclinic.domain.Petclinic;
import is.codion.framework.demos.petclinic.domain.Petclinic.Owner;
import is.codion.framework.demos.petclinic.domain.Petclinic.Pet;
import is.codion.framework.demos.petclinic.domain.Petclinic.PetType;
import is.codion.framework.demos.petclinic.domain.Petclinic.Specialty;
import is.codion.framework.demos.petclinic.domain.Petclinic.Vet;
import is.codion.framework.demos.petclinic.domain.Petclinic.VetSpecialty;
import is.codion.framework.demos.petclinic.domain.Petclinic.Visit;
import is.codion.framework.demos.petclinic.model.PetclinicAppModel;
import is.codion.framework.demos.petclinic.model.VetSpecialtyEditModel;
import is.codion.swing.common.ui.laf.LookAndFeelProvider;
import is.codion.swing.framework.model.SwingEntityModel;
import is.codion.swing.framework.ui.EntityApplicationPanel;
import is.codion.swing.framework.ui.EntityPanel;
import is.codion.swing.framework.ui.ReferentialIntegrityErrorHandling;
import com.formdev.flatlaf.intellijthemes.FlatAllIJThemes;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
public final class PetclinicAppPanel extends EntityApplicationPanel<PetclinicAppModel> {
private static final String DEFAULT_FLAT_LOOK_AND_FEEL = "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialDarkerIJTheme";
public PetclinicAppPanel(PetclinicAppModel appModel) {
super(appModel);
}
@Override
protected List<EntityPanel> createEntityPanels() {
SwingEntityModel ownersModel = applicationModel().entityModel(Owner.TYPE);
SwingEntityModel petsModel = ownersModel.detailModel(Pet.TYPE);
SwingEntityModel visitsModel = petsModel.detailModel(Visit.TYPE);
EntityPanel ownersPanel = new EntityPanel(ownersModel,
new OwnerEditPanel(ownersModel.editModel()));
EntityPanel petsPanel = new EntityPanel(petsModel,
new PetEditPanel(petsModel.editModel()));
EntityPanel visitsPanel = new EntityPanel(visitsModel,
new VisitEditPanel(visitsModel.editModel()));
ownersPanel.addDetailPanel(petsPanel);
petsPanel.addDetailPanel(visitsPanel);
return List.of(ownersPanel);
}
@Override
protected List<EntityPanel.Builder> createSupportEntityPanelBuilders() {
EntityPanel.Builder petTypePanelBuilder =
EntityPanel.builder(PetType.TYPE)
.editPanel(PetTypeEditPanel.class)
.caption("Pet types");
EntityPanel.Builder specialtyPanelBuilder =
EntityPanel.builder(Specialty.TYPE)
.editPanel(SpecialtyEditPanel.class)
.caption("Specialties");
SwingEntityModel.Builder vetSpecialtyModelBuilder =
SwingEntityModel.builder(VetSpecialty.TYPE)
.editModel(VetSpecialtyEditModel.class);
SwingEntityModel.Builder vetModelBuilder =
SwingEntityModel.builder(Vet.TYPE)
.detailModel(vetSpecialtyModelBuilder);
EntityPanel.Builder vetSpecialtyPanelBuilder =
EntityPanel.builder(vetSpecialtyModelBuilder)
.editPanel(VetSpecialtyEditPanel.class)
.caption("Specialty");
EntityPanel.Builder vetPanelBuilder =
EntityPanel.builder(vetModelBuilder)
.editPanel(VetEditPanel.class)
.detailPanel(vetSpecialtyPanelBuilder)
.caption("Vets");
return List.of(petTypePanelBuilder, specialtyPanelBuilder, vetPanelBuilder);
}
public static void main(String[] args) throws CancelException {
Locale.setDefault(new Locale.Builder()
.setLanguage("EN")
.setRegion("en")
.build());
Arrays.stream(FlatAllIJThemes.INFOS).forEach(LookAndFeelProvider::addLookAndFeelProvider);
ReferentialIntegrityErrorHandling.REFERENTIAL_INTEGRITY_ERROR_HANDLING
.set(ReferentialIntegrityErrorHandling.DISPLAY_DEPENDENCIES);
EntityApplicationPanel.builder(PetclinicAppModel.class, PetclinicAppPanel.class)
.applicationName("Petclinic")
.applicationVersion(PetclinicAppModel.VERSION)
.domainType(Petclinic.DOMAIN)
.displayStartupDialog(false)
.defaultLookAndFeelClassName(DEFAULT_FLAT_LOOK_AND_FEEL)
.defaultLoginUser(User.parse("scott:tiger"))
.start();
}
}
Domain unit test
package is.codion.framework.demos.petclinic.domain;
import is.codion.common.db.exception.DatabaseException;
import is.codion.framework.demos.petclinic.domain.Petclinic.Owner;
import is.codion.framework.demos.petclinic.domain.Petclinic.Pet;
import is.codion.framework.demos.petclinic.domain.Petclinic.PetType;
import is.codion.framework.demos.petclinic.domain.Petclinic.Specialty;
import is.codion.framework.demos.petclinic.domain.Petclinic.Vet;
import is.codion.framework.demos.petclinic.domain.Petclinic.VetSpecialty;
import is.codion.framework.demos.petclinic.domain.Petclinic.Visit;
import is.codion.framework.domain.entity.test.EntityTestUnit;
import org.junit.jupiter.api.Test;
public final class PetclinicTest extends EntityTestUnit {
public PetclinicTest() {
super(new Petclinic());
}
@Test
void vet() throws DatabaseException {
test(Vet.TYPE);
}
@Test
void specialty() throws DatabaseException {
test(Specialty.TYPE);
}
@Test
void vetSpecialty() throws DatabaseException {
test(VetSpecialty.TYPE);
}
@Test
void petType() throws DatabaseException {
test(PetType.TYPE);
}
@Test
void owner() throws DatabaseException {
test(Owner.TYPE);
}
@Test
void pet() throws DatabaseException {
test(Pet.TYPE);
}
@Test
void visit() throws DatabaseException {
test(Visit.TYPE);
}
}
Module Info
/**
* Petclinic demo.
*/
module is.codion.framework.demos.petclinic {
requires is.codion.swing.framework.ui;
requires com.formdev.flatlaf.intellijthemes;
exports is.codion.framework.demos.petclinic.model
to is.codion.swing.framework.model, is.codion.swing.framework.ui;
exports is.codion.framework.demos.petclinic.ui
to is.codion.swing.framework.ui;
provides is.codion.framework.domain.Domain
with is.codion.framework.demos.petclinic.domain.Petclinic;
}