Note
|
For the Gradle build configuration see Build section. |
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 each adding a single Entity definition to the domain model. The Domain subsections below continue the Petclinic class.
public final class Petclinic extends DomainModel {
public static final DomainType DOMAIN = domainType("Petclinic");
public Petclinic() {
super(DOMAIN);
add(vet(), specialty(), vetSpecialty(), petType(), owner(), pet(), visit());
}
Display full Petclinic domain model 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 - 2025, Björn Darri Sigurðsson.
*/
package is.codion.demos.petclinic.domain;
import is.codion.demos.petclinic.domain.Petclinic.Owner.PhoneType;
import is.codion.framework.domain.DomainModel;
import is.codion.framework.domain.DomainType;
import is.codion.framework.domain.entity.EntityDefinition;
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.sql.Statement;
import java.time.LocalDate;
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 DomainModel {
public static final DomainType DOMAIN = domainType("Petclinic");
public Petclinic() {
super(DOMAIN);
add(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 EntityDefinition vet() {
return 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)
.build();
}
public interface Specialty {
EntityType TYPE = DOMAIN.entityType("petclinic.specialty");
Column<Integer> ID = TYPE.integerColumn("id");
Column<String> NAME = TYPE.stringColumn("name");
}
private EntityDefinition specialty() {
return 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)
.build();
}
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);
}
private EntityDefinition vetSpecialty() {
return 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())
.build();
}
public interface PetType {
EntityType TYPE = DOMAIN.entityType("petclinic.pet_type");
Column<Integer> ID = TYPE.integerColumn("id");
Column<String> NAME = TYPE.stringColumn("name");
}
private EntityDefinition petType() {
return 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)
.build();
}
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 EntityDefinition owner() {
return 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))
.build();
}
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 EntityDefinition pet() {
return 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))
.build();
}
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 EntityDefinition visit() {
return 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")
.build();
}
}
Owners

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 creating an Owner
interface and defining the domain API constants for the table and its columns, providing the table and column names as parameters.
We also define a PhoneType
enum, which we use as a custom type for 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 along with its columns based on the domain API constants, configuring each for persistance and presentation.
For the Owner.PHONE_TYPE
column we call columnClass()
where we specify the underlying column type and provide a Column.Converter
implementation, for converting the enum to and from the underlying column value.
We use the StringFactory.builder()
method to build a Function<Entity, String>
instance which provides the toString()
implementation for entities of this type.
We also specify a default OrderBy
clause to use when selecting entities of this type.
private EntityDefinition owner() {
return 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))
.build();
}
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, with a constructor taking a SwingEntityEditModel
parameter, which we simply propagate to the super constructor.
We implement the initializeUI()
method in which we create the components using the create…()
methods and add them to the panel using addInputPanel()
.
- NOTE
-
The
create…()
methods return a ComponentBuilder instance, providing a way to configure the input fields.-
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 columns.
-
Set a layout and add the input fields to the panel.
-
package is.codion.demos.petclinic.ui;
import is.codion.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() {
focus().initial().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);
}
}
Pet
SQL
CREATE TABLE petclinic.pet (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(30) NOT NULL,
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
For the Pet
Entity we define a ForeignKey
domain API constant for each foreign key, providing a name along with the columns comprising the underlying reference.
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
We define the foreign keys the same way we define columns.
- NOTE
-
The underlying foreign key reference columns do not have a
caption
specified, which means they will be hidden in table views by default.
private EntityDefinition pet() {
return 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))
.build();
}
UI
For the PetEditPanel
we use createForeignKeyComboBox()
to create a EntityComboBox
for the Pet.OWNER_FK
foreign key, which provides a ComboBox populated with the underlying entities.
For the Pet.PET_TYPE_FK
foreign key we use createForeignKeyComboBoxPanel()
which creates a EntityComboBox
on a panel, which can also include buttons for adding a new item or editing the selected one.
In this case we provide a button for adding a new PetType
by calling add(true)
.
The createForeignKeyComboBoxPanel()
method takes a Supplier<EntityEditPanel>
parameter, responsible for providing the EntityEditPanel
instance to display when adding or editing items.
The createTemporalFieldPanel()
method creates a temporal field for editing dates, on a panel which includes a button for displaying a calendar.
package is.codion.demos.petclinic.ui;
import is.codion.demos.petclinic.domain.Petclinic.Pet;
import is.codion.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() {
focus().initial().set(Pet.NAME);
createComboBox(Pet.OWNER_FK);
createTextField(Pet.NAME);
createComboBoxPanel(Pet.PET_TYPE_FK, this::createPetTypeEditPanel)
.includeAddButton(true);
createTemporalFieldPanel(Pet.BIRTH_DATE);
setLayout(gridLayout(2, 2));
addInputPanel(Pet.OWNER_FK);
addInputPanel(Pet.NAME);
addInputPanel(Pet.PET_TYPE_FK);
addInputPanel(Pet.BIRTH_DATE);
}
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
Here we create a OrderBy
instance using a builder instead of a factory method.
private EntityDefinition visit() {
return 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")
.build();
}
UI
When using a JTextArea
requiring a JScrollPane
we supply the scroll pane containing the text area, retrived via component(Attribute)
, as a parameter when calling addInputPanel()
.
package is.codion.demos.petclinic.ui;
import is.codion.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() {
focus().initial().set(Visit.PET_FK);
createComboBox(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

SQL
CREATE TABLE petclinic.pet_type (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(80) NOT NULL,
CONSTRAINT pet_type_uk UNIQUE (name)
);
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 EntityDefinition petType() {
return 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)
.build();
}
UI
package is.codion.demos.petclinic.ui;
import is.codion.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() {
focus().initial().set(PetType.NAME);
createTextField(PetType.NAME);
setLayout(gridLayout(1, 1));
addInputPanel(PetType.NAME);
}
}
Specialty

SQL
CREATE TABLE petclinic.specialty (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(80) NOT NULL
);
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 EntityDefinition specialty() {
return 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)
.build();
}
UI
package is.codion.demos.petclinic.ui;
import is.codion.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() {
focus().initial().set(Specialty.NAME);
createTextField(Specialty.NAME);
setLayout(gridLayout(1, 1));
addInputPanel(Specialty.NAME);
}
}
Vet

SQL
CREATE TABLE petclinic.vet (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
first_name VARCHAR(30) NOT NULL,
last_name VARCHAR(30) NOT NULL
);
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 EntityDefinition vet() {
return 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)
.build();
}
UI
package is.codion.demos.petclinic.ui;
import is.codion.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() {
focus().initial().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);
}
Implementation
private EntityDefinition vetSpecialty() {
return 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())
.build();
}
Model
Here we extend SwingEntityEditModel
in order to provide validation for a Vet/Specialty combination.
And yes, this should obviously be done with a unique key in the underlying table, but let’s assume we can’t do that for some reason.
Since this validation has some performance cost, due to the select query, we don’t want to add it the the standard form validation, since that is performed every time a value changes.
Instead we add beforeUpdate
and beforeInsert
listeners, which means this validation is only performed before entities are updated or inserted.
In the constructor we initialize the ComboBox models used, creating and populating them, otherwise that would happen during UI initialization.
We also specify that the VetSpecialty.VET_FK
and VetSpecialty.SPECIALTY_FK
values should not persist when the edit model is cleared.
package is.codion.demos.petclinic.model;
import is.codion.demos.petclinic.domain.Petclinic.VetSpecialty;
import is.codion.framework.db.EntityConnectionProvider;
import is.codion.framework.domain.entity.Entity;
import is.codion.framework.domain.entity.exception.ValidationException;
import is.codion.swing.framework.model.SwingEntityEditModel;
import java.util.Collection;
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);
editor().value(VetSpecialty.VET_FK).persist().set(false);
editor().value(VetSpecialty.SPECIALTY_FK).persist().set(false);
beforeUpdate().addConsumer(this::validate);
beforeInsert().addConsumer(this::validate);
}
private void validate(Collection<Entity> entities) {
// Perform the standard validation in order to
// assert that all required values are present
editor().validate(entities);
entities.forEach(this::validate);
}
private void validate(Entity entity) {
int rowCount = 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");
}
}
}
Model Test
package is.codion.demos.petclinic.model;
import is.codion.common.user.User;
import is.codion.demos.petclinic.domain.Petclinic;
import is.codion.demos.petclinic.domain.Petclinic.Specialty;
import is.codion.demos.petclinic.domain.Petclinic.Vet;
import is.codion.demos.petclinic.domain.Petclinic.VetSpecialty;
import is.codion.framework.db.EntityConnection;
import is.codion.framework.db.EntityConnectionProvider;
import is.codion.framework.db.local.LocalEntityConnectionProvider;
import is.codion.framework.domain.entity.Entity;
import is.codion.framework.domain.entity.exception.ValidationException;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertThrows;
public final class VetSpecialtyEditModelTest {
@Test
void validation() {
try (EntityConnectionProvider connectionProvider = createConnectionProvider()) {
EntityConnection connection = connectionProvider.connection();
VetSpecialtyEditModel model = new VetSpecialtyEditModel(connectionProvider);
Entity linda = connection.selectSingle(Vet.FIRST_NAME.equalTo("Linda"));
Entity surgery = connection.selectSingle(Specialty.NAME.equalTo("surgery"));
// Test insert
model.editor().value(VetSpecialty.VET_FK).set(linda);
model.editor().value(VetSpecialty.SPECIALTY_FK).set(surgery);
assertThrows(ValidationException.class, model::insert);
// Test update
List<Entity> specialties = connection.select(VetSpecialty.VET_FK.equalTo(linda));
model.editor().clear();
model.editor().set(specialties.get(0));
model.editor().value(VetSpecialty.SPECIALTY).set(specialties.get(1).get(VetSpecialty.SPECIALTY));
assertThrows(ValidationException.class, model::update);
}
}
private static EntityConnectionProvider createConnectionProvider() {
return LocalEntityConnectionProvider.builder()
.domain(new Petclinic())
.user(User.parse("scott:tiger"))
.build();
}
}
UI
package is.codion.demos.petclinic.ui;
import is.codion.demos.petclinic.domain.Petclinic.Specialty;
import is.codion.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);
}
@Override
protected void initializeUI() {
focus().initial().set(VetSpecialty.VET_FK);
createComboBox(VetSpecialty.VET_FK)
.preferredWidth(200);
createComboBoxPanel(VetSpecialty.SPECIALTY_FK, this::createSpecialtyEditPanel)
.includeAddButton(true)
.preferredWidth(200);
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
The application model holds the SwingEntityModel
instances used by the application, here we create a setupEntityModels()
method for creating and configuring the application model layer.
Note that we initialize ComboBox models and refresh the Owners table model, in order for that not to happen on the EDT when the UI is created.
package is.codion.demos.petclinic.model;
import is.codion.common.version.Version;
import is.codion.demos.petclinic.domain.Petclinic.Owner;
import is.codion.demos.petclinic.domain.Petclinic.Pet;
import is.codion.demos.petclinic.domain.Petclinic.Visit;
import is.codion.framework.db.EntityConnectionProvider;
import is.codion.swing.framework.model.SwingEntityApplicationModel;
import is.codion.swing.framework.model.SwingEntityModel;
import java.util.List;
public final class PetclinicAppModel extends SwingEntityApplicationModel {
public static final Version VERSION = Version.parse(PetclinicAppModel.class, "/version.properties");
public PetclinicAppModel(EntityConnectionProvider connectionProvider) {
super(connectionProvider, List.of(createOwnersModel(connectionProvider)));
}
private static SwingEntityModel createOwnersModel(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.detailModels().add(petsModel);
petsModel.detailModels().add(visitModel);
ownersModel.tableModel().items().refresh();
return ownersModel;
}
}
PetclinicAppPanel
The application panel holds the EntityPanel
instances used by the application, which are created in the createEntityPanels()
method.
These EntityPanels
are based on the SwingEntityModels
we get from the application model.
We also override createSupportPanelBuilders()
, where we create EntityPanel.Builder
instances on which to base the Support tables
main menu.
package is.codion.demos.petclinic.ui;
import is.codion.common.model.CancelException;
import is.codion.common.user.User;
import is.codion.demos.petclinic.domain.Petclinic;
import is.codion.demos.petclinic.domain.Petclinic.Owner;
import is.codion.demos.petclinic.domain.Petclinic.Pet;
import is.codion.demos.petclinic.domain.Petclinic.PetType;
import is.codion.demos.petclinic.domain.Petclinic.Specialty;
import is.codion.demos.petclinic.domain.Petclinic.Vet;
import is.codion.demos.petclinic.domain.Petclinic.Visit;
import is.codion.demos.petclinic.model.PetclinicAppModel;
import is.codion.demos.petclinic.model.VetSpecialtyEditModel;
import is.codion.framework.db.EntityConnectionProvider;
import is.codion.plugin.flatlaf.intellij.themes.arc.Arc;
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 java.util.Collection;
import java.util.List;
import java.util.Locale;
public final class PetclinicAppPanel extends EntityApplicationPanel<PetclinicAppModel> {
public PetclinicAppPanel(PetclinicAppModel appModel) {
super(appModel, createPanels(appModel), createSupportPanelBuilders());
}
private static List<EntityPanel> createPanels(PetclinicAppModel applicationModel) {
SwingEntityModel ownersModel = applicationModel.entityModels().get(Owner.TYPE);
SwingEntityModel petsModel = ownersModel.detailModels().get(Pet.TYPE);
SwingEntityModel visitsModel = petsModel.detailModels().get(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.detailPanels().add(petsPanel);
petsPanel.detailPanels().add(visitsPanel);
return List.of(ownersPanel);
}
private static Collection<EntityPanel.Builder> createSupportPanelBuilders() {
EntityPanel.Builder petTypePanelBuilder =
EntityPanel.builder(PetType.TYPE,
PetclinicAppPanel::createPetTypePanel);
EntityPanel.Builder specialtyPanelBuilder =
EntityPanel.builder(Specialty.TYPE,
PetclinicAppPanel::createSpecialtyPanel);
EntityPanel.Builder vetPanelBuilder =
EntityPanel.builder(Vet.TYPE,
PetclinicAppPanel::createVetPanel);
return List.of(petTypePanelBuilder, specialtyPanelBuilder, vetPanelBuilder);
}
private static EntityPanel createPetTypePanel(EntityConnectionProvider connectionProvider) {
SwingEntityModel petTypeModel =
new SwingEntityModel(PetType.TYPE, connectionProvider);
petTypeModel.tableModel().items().refresh();
return new EntityPanel(petTypeModel,
new PetTypeEditPanel(petTypeModel.editModel()), config ->
config.caption("Pet types"));
}
private static EntityPanel createSpecialtyPanel(EntityConnectionProvider connectionProvider) {
SwingEntityModel specialtyModel =
new SwingEntityModel(Specialty.TYPE, connectionProvider);
specialtyModel.tableModel().items().refresh();
return new EntityPanel(specialtyModel,
new SpecialtyEditPanel(specialtyModel.editModel()), config -> config
.caption("Specialties"));
}
private static EntityPanel createVetPanel(EntityConnectionProvider connectionProvider) {
SwingEntityModel vetModel =
new SwingEntityModel(Vet.TYPE, connectionProvider);
SwingEntityModel vetSpecialtyModel =
new SwingEntityModel(new VetSpecialtyEditModel(connectionProvider));
vetModel.detailModels().add(vetSpecialtyModel);
vetModel.tableModel().items().refresh();
EntityPanel vetPanel = new EntityPanel(vetModel,
new VetEditPanel(vetModel.editModel()), config -> config
.caption("Vets"));
EntityPanel vetSpecialtyPanel = new EntityPanel(vetSpecialtyModel,
new VetSpecialtyEditPanel(vetSpecialtyModel.editModel()), config -> config
.caption("Specialty"));
vetPanel.detailPanels().add(vetSpecialtyPanel);
return vetPanel;
}
public static void main(String[] args) throws CancelException {
Locale.setDefault(Locale.of("en", "EN"));
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)
.defaultLookAndFeel(Arc.class)
.defaultUser(User.parse("scott:tiger"))
.start();
}
}
Domain unit test
package is.codion.demos.petclinic.domain;
import is.codion.demos.petclinic.domain.Petclinic.Owner;
import is.codion.demos.petclinic.domain.Petclinic.Pet;
import is.codion.demos.petclinic.domain.Petclinic.PetType;
import is.codion.demos.petclinic.domain.Petclinic.Specialty;
import is.codion.demos.petclinic.domain.Petclinic.Vet;
import is.codion.demos.petclinic.domain.Petclinic.VetSpecialty;
import is.codion.demos.petclinic.domain.Petclinic.Visit;
import is.codion.framework.domain.test.DomainTest;
import org.junit.jupiter.api.Test;
public final class PetclinicTest extends DomainTest {
public PetclinicTest() {
super(new Petclinic());
}
@Test
void vet() {
test(Vet.TYPE);
}
@Test
void specialty() {
test(Specialty.TYPE);
}
@Test
void vetSpecialty() {
test(VetSpecialty.TYPE);
}
@Test
void petType() {
test(PetType.TYPE);
}
@Test
void owner() {
test(Owner.TYPE);
}
@Test
void pet() {
test(Pet.TYPE);
}
@Test
void visit() {
test(Visit.TYPE);
}
}
Module Info
/**
* Petclinic demo.
*/
module is.codion.demos.petclinic {
requires is.codion.swing.framework.ui;
requires is.codion.plugin.flatlaf;
requires is.codion.plugin.flatlaf.intellij.themes;
exports is.codion.demos.petclinic.model
to is.codion.swing.framework.model, is.codion.swing.framework.ui;
exports is.codion.demos.petclinic.ui
to is.codion.swing.framework.ui;
provides is.codion.framework.domain.Domain
with is.codion.demos.petclinic.domain.Petclinic;
}
Build
settings.gradle
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}
rootProject.name = "petclinic"
dependencyResolutionManagement {
repositories {
mavenCentral()
mavenLocal()
}
versionCatalogs {
libs {
version("codion", "0.18.31")
version("h2", "2.3.232")
library("codion-dbms-h2", "is.codion", "codion-dbms-h2").versionRef("codion")
library("codion-framework-domain-test", "is.codion", "codion-framework-domain-test").versionRef("codion")
library("codion-framework-db-local", "is.codion", "codion-framework-db-local").versionRef("codion")
library("codion-swing-framework-ui", "is.codion", "codion-swing-framework-ui").versionRef("codion")
library("codion-plugin-logback-proxy", "is.codion", "codion-plugin-logback-proxy").versionRef("codion")
library("codion-plugin-flatlaf", "is.codion", "codion-plugin-flatlaf").versionRef("codion")
library("codion-plugin-flatlaf-intellij-themes", "is.codion", "codion-plugin-flatlaf-intellij-themes").versionRef("codion")
library("h2", "com.h2database", "h2").versionRef("h2")
}
}
}
build.gradle.kts
import org.gradle.internal.os.OperatingSystem
plugins {
// The Badass Jlink Plugin provides jlink and jpackage
// functionality and applies the java application plugin
// https://badass-jlink-plugin.beryx.org
id("org.beryx.jlink") version "3.1.1"
// Just for managing the license headers
id("com.diffplug.spotless") version "7.0.1"
// For the asciidoctor docs
id("org.asciidoctor.jvm.convert") version "4.0.4"
}
dependencies {
// The Codion framework UI module, transitively pulls in all required
// modules, such as the model layer and the core database module
implementation(libs.codion.swing.framework.ui)
// Include all the standard Flat Look and Feels and a bunch of IntelliJ
// theme based ones, available via the View -> Select Look & Feel menu
implementation(libs.codion.plugin.flatlaf)
implementation(libs.codion.plugin.flatlaf.intellij.themes)
// Provides the Logback logging library as a transitive dependency
// and provides logging configuration via the Help -> Log menu
runtimeOnly(libs.codion.plugin.logback.proxy)
// Provides the local JDBC connection implementation
runtimeOnly(libs.codion.framework.db.local)
// The H2 database implementation
runtimeOnly(libs.codion.dbms.h2)
// And the H2 database driver
runtimeOnly(libs.h2)
// The domain model unit test module
testImplementation(libs.codion.framework.domain.test)
testImplementation(libs.codion.framework.db.local)
}
// The application version simply follows the Codion framework version used
version = libs.versions.codion.get().replace("-SNAPSHOT", "")
java {
toolchain {
// Use the latest possible Java version
languageVersion.set(JavaLanguageVersion.of(23))
}
}
spotless {
// Just the license headers
java {
licenseHeaderFile("${rootDir}/license_header").yearSeparator(" - ")
}
format("javaMisc") {
target("src/**/package-info.java", "src/**/module-info.java")
licenseHeaderFile("${rootDir}/license_header", "\\/\\*\\*").yearSeparator(" - ")
}
}
testing {
suites {
val test by getting(JvmTestSuite::class) {
useJUnitJupiter()
targets {
all {
// System properties required for running the unit tests
testTask.configure {
// The JDBC url
systemProperty("codion.db.url", "jdbc:h2:mem:h2db")
// The database initialization script
systemProperty("codion.db.initScripts", "classpath:create_schema.sql")
// The user to use when running the tests
systemProperty("codion.test.user", "scott:tiger")
}
}
}
}
}
}
// Configure the application plugin, the jlink plugin relies
// on this configuration when building the runtime image
application {
mainModule = "is.codion.demos.petclinic"
mainClass = "is.codion.demos.petclinic.ui.PetclinicAppPanel"
applicationDefaultJvmArgs = listOf(
// This app doesn't require a lot of memory
"-Xmx64m",
// Specify a local JDBC connection
"-Dcodion.client.connectionType=local",
// The JDBC url
"-Dcodion.db.url=jdbc:h2:mem:h2db",
// The database initialization script
"-Dcodion.db.initScripts=classpath:create_schema.sql",
// Just in case we're debugging in Linux, nevermind
"-Dsun.awt.disablegrab=true"
)
}
tasks.withType<JavaCompile>().configureEach {
options.encoding = "UTF-8"
options.isDeprecation = true
}
// Configure the docs generation
tasks.asciidoctor {
inputs.dir("src")
baseDirFollowsSourceFile()
attributes(
mapOf(
"codion-version" to project.version,
"source-highlighter" to "prettify",
"tabsize" to "2"
)
)
asciidoctorj {
setVersion("2.5.13")
}
}
// Create a version.properties file containing the application version
tasks.register<WriteProperties>("writeVersion") {
destinationFile = file("${temporaryDir.absolutePath}/version.properties")
property("version", libs.versions.codion.get().replace("-SNAPSHOT", ""))
}
// Include the version.properties file from above in the
// application resources, see usage in PetclinicAppModel
tasks.processResources {
from(tasks.named("writeVersion"))
}
// Configure the Jlink plugin
jlink {
// Specify the jlink image name
imageName = project.name
// The options for the jlink task
options = listOf(
"--strip-debug",
"--no-header-files",
"--no-man-pages",
// Add the modular runtimeOnly dependencies, which are handled by the ServiceLoader.
// These don't have an associated 'requires' clause in module-info.java
// and are therefore not added automatically by the jlink plugin.
"--add-modules",
// The local JDBC connection implementation
"is.codion.framework.db.local," +
// The H2 database implementation
"is.codion.dbms.h2," +
// The Logback plugin
"is.codion.plugin.logback.proxy"
)
// H2 database uses slf4j, but is non-modular so the jlink plugin,
// can't derive that dependency, so here we help it along.
addExtraDependencies("slf4j-api")
jpackage {
if (OperatingSystem.current().isLinux) {
icon = "src/main/icons/petclinic.png"
installerOptions = listOf(
"--linux-shortcut"
)
}
if (OperatingSystem.current().isWindows) {
icon = "src/main/icons/petclinic.ico"
installerOptions = listOf(
"--win-menu",
"--win-shortcut"
)
}
}
}
// Copies the documentation to the Codion github pages repository, nevermind
tasks.register<Sync>("copyToGitHubPages") {
group = "documentation"
from(tasks.asciidoctor)
into("../codion-pages/doc/" + project.version + "/tutorials/petclinic")
}