1. Domain model

package is.codion.framework.demos.petstore.domain;

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.StringFactory;
import is.codion.framework.domain.entity.attribute.Column;
import is.codion.framework.domain.entity.attribute.ForeignKey;

import java.math.BigDecimal;

import static is.codion.framework.domain.DomainType.domainType;
import static is.codion.framework.domain.entity.KeyGenerator.sequence;
import static is.codion.framework.domain.entity.OrderBy.ascending;

public final class Petstore extends DomainModel {

  public static final DomainType DOMAIN = domainType(Petstore.class);

  public Petstore() {
    super(DOMAIN);
    add(address(), category(), product(), sellerContactInfo(), item(), tag(), tagItem());
  }

  public interface Address {
    EntityType TYPE = DOMAIN.entityType("address");

    Column<Integer> ID = TYPE.integerColumn("Address id");
    Column<String> STREET_1 = TYPE.stringColumn("Street 1");
    Column<String> STREET_2 = TYPE.stringColumn("Street 2");
    Column<String> CITY = TYPE.stringColumn("City");
    Column<String> STATE = TYPE.stringColumn("State");
    Column<Integer> ZIP = TYPE.integerColumn("Zip");
    Column<Double> LATITUDE = TYPE.doubleColumn("Latitude");
    Column<Double> LONGITUDE = TYPE.doubleColumn("Longitude");
  }

  EntityDefinition address() {
    return Address.TYPE.define(
                    Address.ID.define()
                            .primaryKey()
                            .name("addressid"),
                    Address.STREET_1.define()
                            .column()
                            .caption(Address.STREET_1.name())
                            .name("street1")
                            .maximumLength(55)
                            .nullable(false),
                    Address.STREET_2.define()
                            .column()
                            .caption(Address.STREET_2.name())
                            .name("street2")
                            .maximumLength(55),
                    Address.CITY.define()
                            .column()
                            .caption(Address.CITY.name())
                            .name("city")
                            .maximumLength(55)
                            .nullable(false),
                    Address.STATE.define()
                            .column()
                            .caption(Address.STATE.name())
                            .name("state")
                            .maximumLength(25)
                            .nullable(false),
                    Address.ZIP.define()
                            .column()
                            .caption(Address.ZIP.name())
                            .name("zip")
                            .nullable(false),
                    Address.LATITUDE.define()
                            .column()
                            .caption(Address.LATITUDE.name())
                            .name("latitude")
                            .nullable(false)
                            .maximumFractionDigits(2),
                    Address.LONGITUDE.define()
                            .column()
                            .caption(Address.LONGITUDE.name())
                            .name("longitude")
                            .nullable(false)
                            .maximumFractionDigits(2))
            .tableName("petstore.address")
            .keyGenerator(sequence("petstore.address_seq"))
            .orderBy(ascending(Address.CITY, Address.STREET_1, Address.STREET_2))
            .stringFactory(StringFactory.builder()
                    .value(Address.STREET_1).text(" ")
                    .value(Address.STREET_2).text(", ")
                    .value(Address.CITY).text(" ")
                    .value(Address.ZIP).text(", ")
                    .value(Address.STATE)
                    .build())
            .caption("Addresses")
            .build();
  }

  public interface Category {
    EntityType TYPE = DOMAIN.entityType("category");

    Column<Integer> ID = TYPE.integerColumn("Category id");
    Column<String> NAME = TYPE.stringColumn("Name");
    Column<String> DESCRIPTION = TYPE.stringColumn("Description");
    Column<String> IMAGE_URL = TYPE.stringColumn("Image URL");
  }

  EntityDefinition category() {
    return Category.TYPE.define(
                    Category.ID.define()
                            .primaryKey()
                            .name("categoryid"),
                    Category.NAME.define()
                            .column()
                            .caption(Category.NAME.name())
                            .name("name")
                            .maximumLength(25)
                            .nullable(false),
                    Category.DESCRIPTION.define()
                            .column()
                            .caption(Category.DESCRIPTION.name())
                            .name("description")
                            .maximumLength(255)
                            .nullable(false),
                    Category.IMAGE_URL.define()
                            .column()
                            .caption(Category.IMAGE_URL.name())
                            .name("imageurl")
                            .hidden(true))
            .tableName("petstore.category")
            .keyGenerator(sequence("petstore.category_seq"))
            .orderBy(ascending(Category.NAME))
            .stringFactory(Category.NAME)
            .caption("Categories")
            .build();
  }

  public interface Product {
    EntityType TYPE = DOMAIN.entityType("product");

    Column<Integer> ID = TYPE.integerColumn("Product id");
    Column<Integer> CATEGORY_ID = TYPE.integerColumn("Category id");
    Column<String> NAME = TYPE.stringColumn("Name");
    Column<String> DESCRIPTION = TYPE.stringColumn("Description");
    Column<String> IMAGE_URL = TYPE.stringColumn("Image URL");

    ForeignKey CATEGORY_FK = TYPE.foreignKey("Category", CATEGORY_ID, Category.ID);
  }

  EntityDefinition product() {
    return Product.TYPE.define(
                    Product.ID.define()
                            .primaryKey()
                            .name("productid"),
                    Product.CATEGORY_ID.define()
                            .column()
                            .name("categoryid")
                            .nullable(false),
                    Product.CATEGORY_FK.define()
                            .foreignKey()
                            .caption(Product.CATEGORY_FK.name()),
                    Product.NAME.define()
                            .column()
                            .caption(Product.NAME.name())
                            .name("name")
                            .maximumLength(25)
                            .nullable(false),
                    Product.DESCRIPTION.define()
                            .column()
                            .caption(Product.DESCRIPTION.name())
                            .name("description")
                            .maximumLength(255)
                            .nullable(false),
                    Product.IMAGE_URL.define()
                            .column()
                            .caption(Product.IMAGE_URL.name())
                            .name("imageurl")
                            .maximumLength(55)
                            .hidden(true))
            .tableName("petstore.product")
            .keyGenerator(sequence("petstore.product_seq"))
            .orderBy(ascending(Product.NAME))
            .stringFactory(StringFactory.builder()
                    .value(Product.CATEGORY_FK)
                    .text(" - ")
                    .value(Product.NAME)
                    .build())
            .caption("Products")
            .build();
  }

  public interface SellerContactInfo {
    EntityType TYPE = DOMAIN.entityType("sellercontactinfo");

    Column<Integer> ID = TYPE.integerColumn("Contactinfo id");
    Column<String> FIRST_NAME = TYPE.stringColumn("First name");
    Column<String> LAST_NAME = TYPE.stringColumn("Last name");
    Column<String> EMAIL = TYPE.stringColumn("Email");
  }

  EntityDefinition sellerContactInfo() {
    return SellerContactInfo.TYPE.define(
                    SellerContactInfo.ID.define()
                            .primaryKey()
                            .name("contactinfoid"),
                    SellerContactInfo.FIRST_NAME.define()
                            .column()
                            .caption(SellerContactInfo.FIRST_NAME.name())
                            .searchable(true)
                            .name("firstname")
                            .maximumLength(24)
                            .nullable(false),
                    SellerContactInfo.LAST_NAME.define()
                            .column()
                            .caption(SellerContactInfo.LAST_NAME.name())
                            .searchable(true)
                            .name("lastname")
                            .maximumLength(24)
                            .nullable(false),
                    SellerContactInfo.EMAIL.define()
                            .column()
                            .caption(SellerContactInfo.EMAIL.name())
                            .name("email")
                            .maximumLength(24)
                            .nullable(false))
            .tableName("petstore.sellercontactinfo")
            .keyGenerator(sequence("petstore.sellercontactinfo_seq"))
            .orderBy(ascending(SellerContactInfo.LAST_NAME, SellerContactInfo.FIRST_NAME))
            .stringFactory(StringFactory.builder()
                    .value(SellerContactInfo.LAST_NAME)
                    .text(", ")
                    .value(SellerContactInfo.FIRST_NAME)
                    .build())
            .caption("Seller info")
            .build();
  }

  public interface Item {
    EntityType TYPE = DOMAIN.entityType("item");

    Column<Integer> ID = TYPE.integerColumn("Item id");
    Column<Integer> PRODUCT_ID = TYPE.integerColumn("Product id");
    Column<String> NAME = TYPE.stringColumn("Name");
    Column<String> DESCRIPTION = TYPE.stringColumn("Description");
    Column<String> IMAGE_URL = TYPE.stringColumn("Image URL");
    Column<String> IMAGE_THUMB_URL = TYPE.stringColumn("Image thumbnail URL");
    Column<BigDecimal> PRICE = TYPE.bigDecimalColumn("Price");
    Column<Integer> CONTACT_INFO_ID = TYPE.integerColumn("Contactinfo id");
    Column<Integer> ADDRESS_ID = TYPE.integerColumn("Address id");
    Column<Boolean> DISABLED = TYPE.booleanColumn("Disabled");

    ForeignKey PRODUCT_FK = TYPE.foreignKey("Product", PRODUCT_ID, Product.ID);
    ForeignKey CONTACT_INFO_FK = TYPE.foreignKey("Contact info", CONTACT_INFO_ID, SellerContactInfo.ID);
    ForeignKey ADDRESS_FK = TYPE.foreignKey("Address", ADDRESS_ID, Address.ID);
  }

  EntityDefinition item() {
    return Item.TYPE.define(
                    Item.ID.define()
                            .primaryKey()
                            .name("itemid"),
                    Item.PRODUCT_ID.define()
                            .column()
                            .name("productid")
                            .nullable(false),
                    Item.PRODUCT_FK.define()
                            .foreignKey(2)
                            .caption(Item.PRODUCT_FK.name()),
                    Item.NAME.define()
                            .column()
                            .caption(Item.NAME.name())
                            .name("name")
                            .maximumLength(30)
                            .nullable(false),
                    Item.DESCRIPTION.define()
                            .column()
                            .caption(Item.DESCRIPTION.name())
                            .name("description")
                            .maximumLength(500)
                            .nullable(false),
                    Item.IMAGE_URL.define()
                            .column()
                            .caption(Item.IMAGE_URL.name())
                            .name("imageurl")
                            .maximumLength(55)
                            .hidden(true),
                    Item.IMAGE_THUMB_URL.define()
                            .column()
                            .caption(Item.IMAGE_THUMB_URL.name())
                            .name("imagethumburl")
                            .maximumLength(55)
                            .hidden(true),
                    Item.PRICE.define()
                            .column()
                            .caption(Item.PRICE.name())
                            .name("price")
                            .nullable(false)
                            .maximumFractionDigits(2),
                    Item.CONTACT_INFO_ID.define()
                            .column()
                            .name("contactinfo_contactinfoid")
                            .nullable(false),
                    Item.CONTACT_INFO_FK.define()
                            .foreignKey()
                            .caption(Item.CONTACT_INFO_FK.name()),
                    Item.ADDRESS_ID.define()
                            .column()
                            .name("address_addressid")
                            .nullable(false),
                    Item.ADDRESS_FK.define()
                            .foreignKey()
                            .caption("Address"),
                    Item.DISABLED.define()
                            .booleanColumn(Integer.class, 1, 0)
                            .caption(Item.DISABLED.name())
                            .name("disabled")
                            .defaultValue(false)
                            .nullable(false))
            .tableName("petstore.item")
            .keyGenerator(sequence("petstore.item_seq"))
            .orderBy(ascending(Item.NAME))
            .stringFactory(StringFactory.builder()
                    .value(Item.PRODUCT_FK)
                    .text(" - ")
                    .value(Item.NAME)
                    .build())
            .caption("Items")
            .build();
  }

  public interface Tag {
    EntityType TYPE = DOMAIN.entityType("tag");

    Column<Integer> ID = TYPE.integerColumn("Tag id");
    Column<String> TAG = TYPE.stringColumn("Tag");
    Column<Integer> REFCOUNT = TYPE.integerColumn("Reference count");
  }

  EntityDefinition tag() {
    return Tag.TYPE.define(
                    Tag.ID.define()
                            .primaryKey()
                            .name("tagid"),
                    Tag.TAG.define()
                            .column()
                            .caption(Tag.TAG.name())
                            .name("tag")
                            .maximumLength(30)
                            .nullable(false),
                    Tag.REFCOUNT.define()
                            .subquery("SELECT COUNT(*) FROM petstore.tag_item WHERE tagid = tag.tagid")
                            .caption(Tag.REFCOUNT.name())
                            .name("refcount"))
            .tableName("petstore.tag")
            .keyGenerator(sequence("petstore.tag_seq"))
            .orderBy(ascending(Tag.TAG))
            .selectTableName("petstore.tag tag")
            .stringFactory(Tag.TAG)
            .caption("Tags")
            .build();
  }

  public interface TagItem {
    EntityType TYPE = DOMAIN.entityType("tag_item");

    Column<Integer> ITEM_ID = TYPE.integerColumn("Item id");
    Column<Integer> TAG_ID = TYPE.integerColumn("Tag id");

    ForeignKey ITEM_FK = TYPE.foreignKey("Item", ITEM_ID, Item.ID);
    ForeignKey TAG_FK = TYPE.foreignKey("Tag", TAG_ID, Tag.ID);
  }

  EntityDefinition tagItem() {
    return TagItem.TYPE.define(
                    TagItem.ITEM_ID.define()
                            .primaryKey(0)
                            .name("itemid"),
                    TagItem.ITEM_FK.define()
                            .foreignKey(3)
                            .caption(TagItem.ITEM_FK.name()),
                    TagItem.TAG_ID.define()
                            .primaryKey(1)
                            .name("tagid"),
                    TagItem.TAG_FK.define()
                            .foreignKey()
                            .caption(TagItem.TAG_FK.name()))
            .tableName("petstore.tag_item")
            .stringFactory(StringFactory.builder()
                    .value(TagItem.ITEM_FK)
                    .text(" - ")
                    .value(TagItem.TAG_FK)
                    .build())
            .caption("Item tags")
            .build();
  }
}

1.1. Domain unit test

package is.codion.framework.demos.petstore.domain;

import is.codion.framework.domain.test.DomainTest;

import org.junit.jupiter.api.Test;

import static is.codion.framework.demos.petstore.domain.Petstore.*;

public class PetstoreTest extends DomainTest {

  public PetstoreTest() {
    super(new Petstore());
  }

  @Test
  void address() throws Exception {
    test(Address.TYPE);
  }

  @Test
  void category() throws Exception {
    test(Category.TYPE);
  }

  @Test
  void item() throws Exception {
    test(Item.TYPE);
  }

  @Test
  void product() throws Exception {
    test(Product.TYPE);
  }

  @Test
  void sellerInfo() throws Exception {
    test(SellerContactInfo.TYPE);
  }

  @Test
  void tag() throws Exception {
    test(Tag.TYPE);
  }

  @Test
  void tagItem() throws Exception {
    test(TagItem.TYPE);
  }
}

2. Model

2.1. Main application model

package is.codion.framework.demos.petstore.model;

import is.codion.framework.db.EntityConnectionProvider;
import is.codion.swing.framework.model.SwingEntityApplicationModel;
import is.codion.swing.framework.model.SwingEntityModel;

import static is.codion.framework.demos.petstore.domain.Petstore.*;

public final class PetstoreAppModel extends SwingEntityApplicationModel {

  public PetstoreAppModel(EntityConnectionProvider connectionProvider) {
    super(connectionProvider);
    SwingEntityModel categoryModel = new SwingEntityModel(Category.TYPE, connectionProvider);
    SwingEntityModel productModel = new SwingEntityModel(Product.TYPE, connectionProvider);
    productModel.editModel().initializeComboBoxModels(Product.CATEGORY_FK);
    SwingEntityModel itemModel = new SwingEntityModel(Item.TYPE, connectionProvider);
    itemModel.editModel().initializeComboBoxModels(Item.PRODUCT_FK, Item.CONTACT_INFO_FK, Item.ADDRESS_FK);
    SwingEntityModel tagItemModel = new SwingEntityModel(TagItem.TYPE, connectionProvider);
    tagItemModel.editModel().initializeComboBoxModels(TagItem.ITEM_FK, TagItem.TAG_FK);
    categoryModel.tableModel().refresh();
    itemModel.addDetailModels(tagItemModel);
    productModel.addDetailModels(itemModel);
    categoryModel.addDetailModels(productModel);
    addEntityModel(categoryModel);
  }
}

3. UI

package is.codion.framework.demos.petstore.ui;

import is.codion.swing.common.ui.layout.Layouts;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;

import javax.swing.JLabel;

import static is.codion.framework.demos.petstore.domain.Petstore.Address;

public class AddressEditPanel extends EntityEditPanel {

  public AddressEditPanel(SwingEntityEditModel model) {
    super(model);
  }

  @Override
  protected void initializeUI() {
    initialFocusAttribute().set(Address.CITY);

    createTextField(Address.CITY);
    createTextField(Address.STATE);
    createTextField(Address.ZIP);
    createTextField(Address.STREET_1);
    createTextField(Address.STREET_2);
    createTextField(Address.LATITUDE);
    createTextField(Address.LONGITUDE);

    setLayout(Layouts.flexibleGridLayout(4, 2));
    addInputPanel(Address.CITY);
    addInputPanel(Address.STATE);
    add(new JLabel());
    addInputPanel(Address.ZIP);
    addInputPanel(Address.STREET_1);
    addInputPanel(Address.STREET_2);
    addInputPanel(Address.LATITUDE);
    addInputPanel(Address.LONGITUDE);
  }
}
package is.codion.framework.demos.petstore.ui;

import is.codion.swing.common.ui.layout.Layouts;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;

import static is.codion.framework.demos.petstore.domain.Petstore.Category;

public class CategoryEditPanel extends EntityEditPanel {

  public CategoryEditPanel(SwingEntityEditModel model) {
    super(model);
  }

  @Override
  protected void initializeUI() {
    initialFocusAttribute().set(Category.NAME);

    createTextField(Category.NAME);
    createTextField(Category.DESCRIPTION).columns(18);
    createTextField(Category.IMAGE_URL);

    setLayout(Layouts.flexibleGridLayout(2, 2));
    addInputPanel(Category.NAME);
    addInputPanel(Category.DESCRIPTION);
    addInputPanel(Category.IMAGE_URL);
  }
}
package is.codion.framework.demos.petstore.ui;

import is.codion.swing.common.ui.layout.Layouts;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;

import static is.codion.framework.demos.petstore.domain.Petstore.SellerContactInfo;

public class ContactInfoEditPanel extends EntityEditPanel {

  public ContactInfoEditPanel(SwingEntityEditModel model) {
    super(model);
  }

  @Override
  protected void initializeUI() {
    initialFocusAttribute().set(SellerContactInfo.LAST_NAME);

    createTextField(SellerContactInfo.LAST_NAME);
    createTextField(SellerContactInfo.FIRST_NAME);
    createTextField(SellerContactInfo.EMAIL);

    setLayout(Layouts.flexibleGridLayout(3, 1));
    addInputPanel(SellerContactInfo.LAST_NAME);
    addInputPanel(SellerContactInfo.FIRST_NAME);
    addInputPanel(SellerContactInfo.EMAIL);
  }
}
package is.codion.framework.demos.petstore.ui;

import is.codion.swing.common.ui.layout.Layouts;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;

import static is.codion.framework.demos.petstore.domain.Petstore.*;

public class ItemEditPanel extends EntityEditPanel {

  public ItemEditPanel(SwingEntityEditModel model) {
    super(model);
    defaults().textFieldColumns().set(14);
    defaults().foreignKeyComboBoxPreferredWidth().set(180);
  }

  @Override
  protected void initializeUI() {
    initialFocusAttribute().set(Item.PRODUCT_FK);

    createForeignKeyComboBox(Item.PRODUCT_FK);
    createTextField(Item.NAME);
    createTextFieldPanel(Item.DESCRIPTION)
            .buttonFocusable(false);
    createTextField(Item.PRICE);
    createForeignKeyComboBoxPanel(Item.CONTACT_INFO_FK, this::createContactInfoEditPanel)
            .includeAddButton(true);
    createForeignKeyComboBoxPanel(Item.ADDRESS_FK, this::createAddressEditPanel)
            .includeAddButton(true);
    createTextField(Item.IMAGE_URL);
    createTextField(Item.IMAGE_THUMB_URL);
    createCheckBox(Item.DISABLED);

    setLayout(Layouts.flexibleGridLayout(3, 3));
    addInputPanel(Item.PRODUCT_FK);
    addInputPanel(Item.NAME);
    addInputPanel(Item.DESCRIPTION);
    addInputPanel(Item.PRICE);
    addInputPanel(Item.CONTACT_INFO_FK);
    addInputPanel(Item.ADDRESS_FK);
    addInputPanel(Item.IMAGE_URL);
    addInputPanel(Item.IMAGE_THUMB_URL);
    addInputPanel(Item.DISABLED);
  }

  private ContactInfoEditPanel createContactInfoEditPanel() {
    return new ContactInfoEditPanel(new SwingEntityEditModel(SellerContactInfo.TYPE, editModel().connectionProvider()));
  }

  private AddressEditPanel createAddressEditPanel() {
    return new AddressEditPanel(new SwingEntityEditModel(Address.TYPE, editModel().connectionProvider()));
  }
}
package is.codion.framework.demos.petstore.ui;

import is.codion.swing.common.ui.layout.Layouts;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;

import static is.codion.framework.demos.petstore.domain.Petstore.Product;

public class ProductEditPanel extends EntityEditPanel {

  public ProductEditPanel(SwingEntityEditModel model) {
    super(model);
  }

  @Override
  protected void initializeUI() {
    initialFocusAttribute().set(Product.CATEGORY_FK);

    createForeignKeyComboBox(Product.CATEGORY_FK);
    createTextField(Product.NAME);
    createTextField(Product.DESCRIPTION).columns(16);

    setLayout(Layouts.flexibleGridLayout(3, 1));
    addInputPanel(Product.CATEGORY_FK);
    addInputPanel(Product.NAME);
    addInputPanel(Product.DESCRIPTION);
  }
}
package is.codion.framework.demos.petstore.ui;

import is.codion.framework.demos.petstore.domain.Petstore.Tag;
import is.codion.framework.demos.petstore.domain.Petstore.TagItem;
import is.codion.swing.common.ui.layout.Layouts;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;

public class TagItemEditPanel extends EntityEditPanel {

  public TagItemEditPanel(SwingEntityEditModel model) {
    super(model);
  }

  @Override
  protected void initializeUI() {
    initialFocusAttribute().set(TagItem.ITEM_FK);
    createForeignKeyComboBox(TagItem.ITEM_FK)
            .preferredWidth(180);
    createForeignKeyComboBoxPanel(TagItem.TAG_FK, this::createTagEditPanel)
            .includeAddButton(true);
    setLayout(Layouts.flexibleGridLayout(2, 1));
    addInputPanel(TagItem.ITEM_FK);
    addInputPanel(TagItem.TAG_FK);
  }

  private TagEditPanel createTagEditPanel() {
    return new TagEditPanel(new SwingEntityEditModel(Tag.TYPE, editModel().connectionProvider()));
  }
}
package is.codion.framework.demos.petstore.ui;

import is.codion.framework.demos.petstore.domain.Petstore.Tag;
import is.codion.swing.common.ui.layout.Layouts;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;

public class TagEditPanel extends EntityEditPanel {

  public TagEditPanel(SwingEntityEditModel model) {
    super(model);
  }

  @Override
  protected void initializeUI() {
    initialFocusAttribute().set(Tag.TAG);

    createTextField(Tag.TAG).columns(16);

    setLayout(Layouts.flexibleGridLayout(1, 1));
    addInputPanel(Tag.TAG);
  }
}

4. Main application panel

package is.codion.framework.demos.petstore.ui;

import is.codion.common.user.User;
import is.codion.framework.demos.petstore.model.PetstoreAppModel;
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.TabbedDetailLayout;

import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import static is.codion.framework.demos.petstore.domain.Petstore.*;
import static is.codion.swing.framework.ui.EntityPanel.PanelState.HIDDEN;

public final class PetstoreAppPanel extends EntityApplicationPanel<PetstoreAppModel> {

  public PetstoreAppPanel(PetstoreAppModel applicationModel) {
    super(applicationModel);
  }

  @Override
  protected List<EntityPanel> createEntityPanels() {
    /* CATEGORY
     *   PRODUCT
     *     ITEM
     *       ITEMTAG
     */
    SwingEntityModel categoryModel = applicationModel().entityModel(Category.TYPE);
    SwingEntityModel productModel = categoryModel.detailModel(Product.TYPE);
    SwingEntityModel itemModel = productModel.detailModel(Item.TYPE);
    SwingEntityModel tagItemModel = itemModel.detailModel(TagItem.TYPE);

    EntityPanel categoryPanel = new EntityPanel(categoryModel,
            new CategoryEditPanel(categoryModel.editModel()),
            config -> config.detailLayout(entityPanel -> TabbedDetailLayout.builder(entityPanel)
                    .splitPaneResizeWeight(0.3)
                    .build()));
    EntityPanel productPanel = new EntityPanel(productModel,
            new ProductEditPanel(productModel.editModel()));
    EntityPanel itemPanel = new EntityPanel(itemModel,
            new ItemEditPanel(itemModel.editModel()),
            config -> config.detailLayout(entityPanel -> TabbedDetailLayout.builder(entityPanel)
                    .initialDetailState(HIDDEN)
                    .build()));
    EntityPanel tagItemPanel = new EntityPanel(tagItemModel,
            new TagItemEditPanel(tagItemModel.editModel()));

    categoryPanel.addDetailPanel(productPanel);
    productPanel.addDetailPanel(itemPanel);
    itemPanel.addDetailPanels(tagItemPanel);

    return List.of(categoryPanel);
  }

  @Override
  protected List<EntityPanel.Builder> createSupportEntityPanelBuilders() {
    SwingEntityModel.Builder tagModelBuilder =
            SwingEntityModel.builder(Tag.TYPE)
                    .detailModel(SwingEntityModel.builder(TagItem.TYPE));
    SwingEntityModel.Builder sellerContactInfoModelBuilder =
            SwingEntityModel.builder(SellerContactInfo.TYPE)
                    .detailModel(SwingEntityModel.builder(Item.TYPE)
                            .detailModel(SwingEntityModel.builder(TagItem.TYPE)));

    return Arrays.asList(
            EntityPanel.builder(Address.TYPE)
                    .editPanel(AddressEditPanel.class),
            EntityPanel.builder(sellerContactInfoModelBuilder)
                    .editPanel(ContactInfoEditPanel.class)
                    .detailPanel(EntityPanel.builder(Item.TYPE)
                            .editPanel(ItemEditPanel.class)
                            .detailPanel(EntityPanel.builder(TagItem.TYPE)
                                    .editPanel(TagItemEditPanel.class))
                            .detailLayout(entityPanel -> TabbedDetailLayout.builder(entityPanel)
                                    .initialDetailState(HIDDEN)
                                    .build())),
            EntityPanel.builder(tagModelBuilder)
                    .editPanel(TagEditPanel.class)
                    .detailPanel(EntityPanel.builder(TagItem.TYPE)
                            .editPanel(TagItemEditPanel.class))
                    .detailLayout(entityPanel -> TabbedDetailLayout.builder(entityPanel)
                            .initialDetailState(HIDDEN)
                            .build()));
  }

  public static void main(String[] args) {
    Locale.setDefault(new Locale("en"));
    EntityPanel.Config.TOOLBAR_CONTROLS.set(true);
    EntityApplicationPanel.builder(PetstoreAppModel.class, PetstoreAppPanel.class)
            .applicationName("The Pet Store")
            .domainType(DOMAIN)
            .defaultLoginUser(User.parse("scott:tiger"))
            .start();
  }
}

5. Load test

package is.codion.framework.demos.petstore.testing;

import is.codion.common.user.User;
import is.codion.framework.db.EntityConnectionProvider;
import is.codion.framework.demos.petstore.domain.Petstore;
import is.codion.framework.demos.petstore.model.PetstoreAppModel;
import is.codion.framework.model.EntityTableModel;
import is.codion.swing.framework.model.SwingEntityModel;
import is.codion.tools.loadtest.LoadTest;
import is.codion.tools.loadtest.LoadTest.Scenario;
import is.codion.tools.loadtest.LoadTest.Scenario.Performer;

import java.util.List;
import java.util.Random;
import java.util.function.Function;

import static is.codion.tools.loadtest.model.LoadTestModel.loadTestModel;
import static is.codion.tools.loadtest.ui.LoadTestPanel.loadTestPanel;

public final class PetstoreLoadTest {

  private static final User UNIT_TEST_USER =
          User.parse(System.getProperty("codion.test.user", "scott:tiger"));

  private static final class PetstoreAppModelFactory
          implements Function<User, PetstoreAppModel> {

    @Override
    public PetstoreAppModel apply(User user) {
      PetstoreAppModel applicationModel = new PetstoreAppModel(
              EntityConnectionProvider.builder()
                      .domainType(Petstore.DOMAIN)
                      .clientTypeId(getClass().getSimpleName())
                      .user(user)
                      .build());
      SwingEntityModel categoryModel = applicationModel.entityModels().iterator().next();
      categoryModel.detailModelLink(categoryModel.detailModels().iterator().next()).active().set(true);
      SwingEntityModel productModel = categoryModel.detailModels().iterator().next();
      productModel.detailModelLink(productModel.detailModels().iterator().next()).active().set(true);
      SwingEntityModel itemModel = productModel.detailModels().iterator().next();
      itemModel.detailModelLink(itemModel.detailModels().iterator().next()).active().set(true);

      return applicationModel;
    }
  }

  private static final class PetstoreUsageScenario
          implements Performer<PetstoreAppModel> {

    private static final Random RANDOM = new Random();

    @Override
    public void perform(PetstoreAppModel application) throws Exception {
      SwingEntityModel categoryModel = application.entityModels().iterator().next();
      categoryModel.tableModel().selection().clear();
      categoryModel.tableModel().refresh();
      selectRandomRow(categoryModel.tableModel());
      selectRandomRow(categoryModel.detailModels().iterator().next().tableModel());
      selectRandomRow(categoryModel.detailModels().iterator().next().detailModels().iterator().next().tableModel());
    }

    private static void selectRandomRow(EntityTableModel<?> tableModel) {
      if (tableModel.items().visible().count() > 0) {
        tableModel.selection().index().set(RANDOM.nextInt(tableModel.items().visible().count()));
      }
    }
  }

  public static void main(String[] args) {
    LoadTest<PetstoreAppModel> loadTest =
            LoadTest.builder(new PetstoreAppModelFactory(),
                            application -> application.connectionProvider().close())
                    .user(UNIT_TEST_USER)
                    .scenarios(List.of(Scenario.builder(new PetstoreUsageScenario())
                            .name("selectRecords")
                            .build()))
                    .name("Petstore LoadTest - " + EntityConnectionProvider.CLIENT_CONNECTION_TYPE.get())
                    .build();
    loadTestPanel(loadTestModel(loadTest)).run();
  }
}