1. Domain model

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

import is.codion.framework.domain.DefaultDomain;
import is.codion.framework.domain.DomainType;
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 DefaultDomain {

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

  public Petstore() {
    super(DOMAIN);
    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");
  }

  void address() {
    add(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"));
  }

  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");
  }

  void category() {
    add(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"));
  }

  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);
  }

  void product() {
    add(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"));
  }

  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");
  }

  void sellerContactInfo() {
    add(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"));
  }

  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);
  }

  void item() {
    add(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"));
  }

  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");
  }

  void tag() {
    add(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"));
  }

  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);
  }

  void tagItem() {
    add(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"));
  }
}

1.1. Domain unit test

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

import is.codion.framework.domain.entity.test.EntityTestUnit;

import org.junit.jupiter.api.Test;

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

public class PetstoreTest extends EntityTestUnit {

  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)
            .add(true);
    createForeignKeyComboBoxPanel(Item.ADDRESS_FK, this::createAddressEditPanel)
            .add(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)
            .add(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.common.ui.Windows;
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.EntityPanel.PanelState;
import is.codion.swing.framework.ui.TabbedPanelLayout;

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

import static is.codion.framework.demos.petstore.domain.Petstore.*;
import static is.codion.swing.framework.ui.TabbedPanelLayout.detailPanelState;

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()),
            TabbedPanelLayout.splitPaneResizeWeight(0.3));
    EntityPanel productPanel = new EntityPanel(productModel,
            new ProductEditPanel(productModel.editModel()));
    EntityPanel itemPanel = new EntityPanel(itemModel,
            new ItemEditPanel(itemModel.editModel()),
            detailPanelState(PanelState.HIDDEN));
    EntityPanel tagItemPanel = new EntityPanel(tagItemModel,
            new TagItemEditPanel(tagItemModel.editModel()));

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

    return Collections.singletonList(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))
                            .layout(detailPanelState(PanelState.HIDDEN))),
            EntityPanel.builder(tagModelBuilder)
                    .editPanel(TagEditPanel.class)
                    .detailPanel(EntityPanel.builder(TagItem.TYPE)
                            .editPanel(TagItemEditPanel.class))
                    .layout(detailPanelState(PanelState.HIDDEN)));
  }

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

5. Load test

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

import is.codion.common.model.CancelException;
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.swing.common.model.tools.loadtest.AbstractUsageScenario;
import is.codion.swing.common.ui.tools.loadtest.LoadTestPanel;
import is.codion.swing.framework.model.SwingEntityModel;
import is.codion.swing.framework.model.tools.loadtest.EntityLoadTestModel;

import static java.util.Collections.singletonList;

public final class PetstoreLoadTest extends EntityLoadTestModel<PetstoreAppModel> {

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

  public PetstoreLoadTest() {
    super(UNIT_TEST_USER, singletonList(new AbstractUsageScenario<PetstoreAppModel>("selectRecords") {
      @Override
      protected void perform(PetstoreAppModel application) {
        SwingEntityModel categoryModel = application.entityModels().iterator().next();
        categoryModel.tableModel().selectionModel().clearSelection();
        categoryModel.tableModel().refresh();
        selectRandomRow(categoryModel.tableModel());
        selectRandomRow(categoryModel.detailModels().iterator().next().tableModel());
        selectRandomRow(categoryModel.detailModels().iterator().next().detailModels().iterator().next().tableModel());
      }
    }));
  }

  @Override
  protected PetstoreAppModel createApplication(User user) throws CancelException {
    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;
  }

  public static void main(String[] args) {
    new LoadTestPanel<>(new PetstoreLoadTest().loadTestModel()).run();
  }
}