1. Framework Model Architecture

The framework model layer serves as the bridge between the domain layer’s entity definitions and the UI layer’s visual components. It provides a comprehensive set of model classes that handle data retrieval, manipulation, and state management while maintaining strict separation of concerns.

1.1. Overview

The model layer implements a reactive MVC architecture where:

  • Models manage data and business logic through observable state

  • Views (UI layer) observe models for state changes

  • Controllers are the reactive bindings (like ComponentValue) that automatically synchronize models and views

All model components are built on Codion’s observable foundation, ensuring that any data or state change automatically propagates to interested observers.

1.2. Core Components

1.2.1. EntityModel

The EntityModel serves as the central coordinator, managing both edit and table models along with any detail models in master-detail relationships.

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityModel invoiceModel = new SwingEntityModel(Invoice.TYPE, connectionProvider);

    // Establish master-detail relationship
    customerModel.detailModels().add(invoiceModel);

1.2.2. EntityEditModel

The EntityEditModel handles single entity CRUD operations and maintains the current entity state.

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    EntityEditModel editModel = customerModel.editModel();

    // Access entity values
    Value<String> nameValue = editModel.editor().value(Customer.FIRSTNAME);

    // Perform operations
    editModel.insert();
    editModel.update();
    editModel.delete();

1.2.3. EntityTableModel

The EntityTableModel manages collections of entities, providing sorting, filtering, and selection capabilities.

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    EntityTableModel tableModel = customerModel.tableModel();

    // Refresh data
    tableModel.items().refresh();

    // Access selection
    Collection<Entity> selected = tableModel.selection().items().get();

1.2.4. EntityQueryModel

The EntityQueryModel controls how data is fetched from the database, including conditions, limits, and ordering.

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityTableModel tableModel = customerModel.tableModel();
    EntityQueryModel queryModel = tableModel.queryModel();

    // Configure query behavior
    queryModel.limit().set(200);
    queryModel.conditionRequired().set(true);
    queryModel.orderBy().set(OrderBy.ascending(Customer.LASTNAME));

1.3. Observable Architecture

Every model component extends Codion’s observable foundation:

1.3.1. State Management

Models expose their state through observable values:

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    EntityEditModel editModel = customerModel.editModel();
    EntityTableModel tableModel = customerModel.tableModel();

    // Edit model states
    State updateEnabled = editModel.updateEnabled();
    State updateMultipleEnabled = editModel.updateMultipleEnabled();
    ObservableState modified = editModel.editor().modified();

    // Table model states
    ObservableState refreshing = tableModel.items().refresher().active();
    ObservableState hasSelection = tableModel.selection().empty().not();

    // Combine states
    ObservableState canDelete = State.and(hasSelection, refreshing.not());

1.3.2. Event System

Models emit events for all significant operations:

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityEditModel editModel = customerModel.editModel();
    SwingEntityTableModel tableModel = customerModel.tableModel();

    // Listen for entity changes
    editModel.afterInsert().addConsumer(entities -> {
      System.out.println("Inserted: " + entities);
    });

    // Listen for selection changes
    tableModel.selection().item().addConsumer(selectedEntity -> {
      if (selectedEntity != null) {
        loadDetails(selectedEntity);
      }
    });

1.3.3. Value Observers

Entity values are observable and can be bound across models:

    SwingEntityModel trackModel = new SwingEntityModel(Track.TYPE, connectionProvider);
    EntityEditModel editModel = trackModel.editModel();

    // Bind edit model value to UI state
    EditorValue<BigDecimal> priceValue = editModel.editor().value(Track.UNITPRICE);
    ObservableState priceValid = editModel.editor().value(Track.UNITPRICE).valid();

    // React to value changes
    priceValue.addConsumer(newPrice -> updateTotalPrice(newPrice));

    // React to value edits
    priceValue.edited().addConsumer(newPrice -> System.out.println("Price: " + newPrice));

1.4. Model Relationships

1.4.1. Master-Detail Pattern

The framework supports arbitrarily deep master-detail hierarchies:

    // Three-level hierarchy
    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityModel invoiceModel = new SwingEntityModel(Invoice.TYPE, connectionProvider);
    SwingEntityModel invoiceLineModel = new SwingEntityModel(InvoiceLine.TYPE, connectionProvider);

    customerModel.detailModels().add(invoiceModel);
    invoiceModel.detailModels().add(invoiceLineModel);

    // Selection cascades down the hierarchy
    Entity customer = getCustomer(connectionProvider);
    customerModel.tableModel().selection().item().set(customer);
    // Invoices for selected customer are loaded
    Entity invoice = invoiceModel.tableModel().items().visible().get(0);
    invoiceModel.tableModel().selection().item().set(invoice);
    // Invoice lines for selected invoice are loaded

1.5. Best Practices

1.5.1. Query Optimization

Always configure appropriate limits to prevent loading excessive data:

    class CustomerTableModel extends SwingEntityTableModel {

      public CustomerTableModel(EntityConnectionProvider connectionProvider) {
        super(new SwingEntityEditModel(Customer.TYPE, connectionProvider));
        // Prevent loading entire customer base
        queryModel().limit().set(100);
        queryModel().conditionRequired().set(true);
      }
    }

1.5.2. Event Handling

Use the event system to maintain consistency across models:

    SwingEntityModel invoiceLineModel = new SwingEntityModel(InvoiceLine.TYPE, connectionProvider);

    // Update summary when details change
    invoiceLineModel.editModel().afterInsert().addConsumer(entities -> updateInvoiceTotal());
    invoiceLineModel.editModel().afterUpdate().addConsumer(entities -> updateInvoiceTotal());
    invoiceLineModel.editModel().afterDelete().addConsumer(entities -> updateInvoiceTotal());

1.5.3. Custom Data Sources

For specialized queries, consider custom data sources:

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityTableModel tableModel = customerModel.tableModel();

    // Fetch only Customers with emails by default
    tableModel.queryModel().dataSource().set(queryModel -> {
      EntityConnection connection = queryModel.connectionProvider().connection();

      return connection.select(and(
              Customer.EMAIL.isNotNull(),
              queryModel.condition().where(Conjunction.AND))
      );
    });

1.6. See Also

2. EntityModel

The application model layer consists of the EntityModel class and its associates; the EntityTableModel, which provides a table representation of entities and the EntityEditModel which provides the CRUD operations.

An EntityModel always contains an EntityEditModel instance and usually contains a EntityTableModel as well. A default edit model implementation is created automatically by the EntityTableModel if one is not supplied via a constructor argument.

entity model diagram
public class AddressModel extends SwingEntityModel {

  public AddressModel(EntityConnectionProvider connectionProvider) {
    super(Address.TYPE, connectionProvider);
  }
}
public class CustomerAddressModel extends SwingEntityModel {

  public CustomerAddressModel(EntityConnectionProvider connectionProvider) {
    super(new CustomerAddressTableModel(connectionProvider));
  }
}

2.1. Detail models

An EntityModel can contain one or more detail models, usually based on foreign key relationships.

entity detail model diagram
public class StoreApplicationModel extends SwingEntityApplicationModel {

  public StoreApplicationModel(EntityConnectionProvider connectionProvider) {
    super(connectionProvider, List.of(createCustomerModel(connectionProvider)));
  }

  private static SwingEntityModel createCustomerModel(EntityConnectionProvider connectionProvider) {
    CustomerModel customerModel =
            new CustomerModel(connectionProvider);
    CustomerAddressModel customerAddressModel =
            new CustomerAddressModel(connectionProvider);

    customerModel.detailModels().add(customerAddressModel);

    //populate the model with rows from the database
    customerModel.tableModel().items().refresh();

    return customerModel;
  }
}

2.2. Event binding

The model layer classes expose a number of Event, State and Value observers.

The example below prints, to the standard output, all changes made to a given attribute value as well as a message indicating that a table refresh has started.

public class CustomerModel extends SwingEntityModel {

  public CustomerModel(EntityConnectionProvider connectionProvider) {
    super(new CustomerTableModel(connectionProvider));
    bindEvents();
  }

  private void bindEvents() {
    CustomerTableModel tableModel = (CustomerTableModel) tableModel();

    tableModel.selection().items()
            .addConsumer(selected ->
                    System.out.println("Items selected: " + selected));

    tableModel.items().refresher().result()
            .addListener(() -> System.out.println("Refresh successful"));

    CustomerEditModel editModel = (CustomerEditModel) editModel();

    editModel.afterInsert()
            .addConsumer(inserted ->
                            System.out.println("Entities inserted" + inserted));

    editModel.editor().value(Customer.FIRST_NAME).edited()
            .addConsumer(firstName ->
                    System.out.println("First name changed to " + firstName));
  }
}

2.3. Examples

3. EntityEditModel

entity edit model diagram

The EntityEditModel interface defines the CRUD business logic used by the EntityEditPanel class when entities are being edited. The EntityEditModel works with a single entity instance, which can be retrieved and set via the EntityEditor instance accessible via the editor() method. EntityEditor exposes a number of methods for manipulating as well as querying the state of the entity being edited.

public class CustomerEditModel extends SwingEntityEditModel {

  public CustomerEditModel(EntityConnectionProvider connectionProvider) {
    super(Customer.TYPE, connectionProvider);
  }
}
EntityConnectionProvider connectionProvider =
        EntityConnectionProvider.builder()
                .domainType(Store.DOMAIN)
                .user(User.parse("scott:tiger"))
                .clientType("StoreMisc")
                .build();

CustomerEditModel editModel = new CustomerEditModel(connectionProvider);

EntityEditor editor = editModel.editor();
editor.value(Customer.ID).defaultValue()
        .set(() -> UUID.randomUUID().toString());

//sets the defaults
editor.defaults();
//set the values
editor.value(Customer.FIRST_NAME).set("Björn");
editor.value(Customer.LAST_NAME).set("Sigurðsson");
editor.value(Customer.ACTIVE).set(true);

//inserts and returns the inserted entity
Entity customer = editModel.insert();

//modify some values
editor.value(Customer.FIRST_NAME).set("John");
editor.value(Customer.LAST_NAME).set("Doe");

//updates and returns the updated entity
customer = editModel.update();

//deletes the active entity
editModel.delete();

4. EntityTableModel

entity table model diagram

The EntityTableModel class provides a table representation of the underlying entities.

Every EntityTableModel contains a EntityEditModel instance. A default edit model implementation is created automatically by the EntityTableModel if one is not supplied via a constructor argument.

public class CustomerTableModel extends SwingEntityTableModel {

  public CustomerTableModel(EntityConnectionProvider connectionProvider) {
    super(new CustomerEditModel(connectionProvider));
  }
}
public class CustomerAddressTableModel extends SwingEntityTableModel {

  public CustomerAddressTableModel(EntityConnectionProvider connectionProvider) {
    super(CustomerAddress.TYPE, connectionProvider);
  }
}

5. EntityQueryModel

The EntityQueryModel manages how entities are fetched from the database for table models. It provides fine-grained control over query conditions, result limits, ordering, and custom data sources.

5.1. Overview

EntityQueryModel acts as the data retrieval engine for EntityTableModel, encapsulating:

  • Query conditions (WHERE and HAVING clauses)

  • Result limits to prevent excessive data loading

  • Custom ordering specifications

  • Attribute selection for optimization

  • Custom data sources for specialized queries

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityTableModel tableModel = customerModel.tableModel();
    EntityQueryModel queryModel = tableModel.queryModel();

    // Configure query behavior
    queryModel.limit().set(200);
    queryModel.conditionRequired().set(true);
    queryModel.orderBy().set(OrderBy.ascending(Customer.LASTNAME));

5.2. Condition Management

5.2.1. Table Condition Model

The primary condition mechanism is the EntityTableConditionModel, which provides a flexible way to build complex queries:

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    EntityTableConditionModel conditionModel = customerModel.tableModel().queryModel().condition();

    // Set condition values
    conditionModel.get(Customer.EMAIL).set().isNotNull();
    conditionModel.get(Customer.COUNTRY).set().equalTo("Iceland");

    // The resulting query will include:
    // WHERE email is not null AND country = 'Iceland'

5.2.2. Additional WHERE Conditions

Beyond the table condition model, you can add custom WHERE conditions:

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    EntityQueryModel queryModel = customerModel.tableModel().queryModel();

    // Single additional condition
    queryModel.where().conjunction().set(Conjunction.AND);
    queryModel.where().set(() -> Customer.COUNTRY.equalTo("Iceland"));

    // Multiple conditions with custom conjunction
    queryModel.where().set(() -> Condition.or(
            Customer.CITY.equalTo("Reykjavik"),
            Customer.CITY.equalTo("Akureyri")
    ));

5.3. Query Limits

Prevent loading excessive data by setting query limits:

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    EntityQueryModel queryModel = customerModel.tableModel().queryModel();

    // Set a specific limit
    queryModel.limit().set(500);

    // Remove limit (fetch all matching rows)
    queryModel.limit().clear();

    // Add a max limit validator
    queryModel.limit().addValidator(newLimit -> {
      if (newLimit > 10.000) {
        throw new IllegalArgumentException("Limit may not exceed 10.000");
      }
    });

    // Listen for limit changes
    queryModel.limit().addConsumer(newLimit ->
            System.out.println("Query limit changed to: " + newLimit));

5.4. Result Ordering

Specify how results should be ordered:

    SwingEntityModel invoiceModel = new SwingEntityModel(Invoice.TYPE, connectionProvider);
    EntityQueryModel queryModel = invoiceModel.tableModel().queryModel();

    // Single column ordering
    queryModel.orderBy().set(OrderBy.descending(Invoice.DATE));

    // Multiple columns
    queryModel.orderBy().set(OrderBy.builder()
            .ascending(Invoice.BILLINGCOUNTRY)
            .descending(Invoice.DATE)
            .build()
    );

5.5. Custom Data Sources

For complex queries that can’t be expressed through conditions, provide a custom data source:

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    EntityQueryModel entityQueryModel = customerModel.tableModel().queryModel();

    entityQueryModel.dataSource().set(queryModel -> {
      EntityConnection connection = queryModel.connectionProvider().connection();

      // Custom query with complex joins or database-specific features
      return connection.select(Select.where(customComplexCondition())
              .attributes(Customer.ADDRESS, Customer.CITY, Customer.COUNTRY)
              .build());
    });

5.6. Condition Required

Prevent accidental full table scans:

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    EntityQueryModel queryModel = customerModel.tableModel().queryModel();

    // Require at least one condition
    queryModel.conditionRequired().set(true);

    // Specify that a certain condition must be enabled
    queryModel.conditionEnabled().set(queryModel.condition().get(Customer.SUPPORTREP_FK).enabled());

5.7. Attribute Management

Optimize queries by selecting only needed attributes:

    SwingEntityModel albumModel = new SwingEntityModel(Album.TYPE, connectionProvider);
    EntityQueryModel queryModel = albumModel.tableModel().queryModel();

    // Exclude large columns by default
    queryModel.attributes().excluded().add(Album.COVER);

    // Include them only when needed
    State detailView = State.state();
    detailView.addConsumer(showDetails -> {
      if (showDetails) {
        queryModel.attributes().included().add(Album.COVER);
      }
    });

5.8. Configuration Properties

Table 1. EntityQueryModel Configuration
Property Default Description

is.codion.framework.model.EntityTableModel.LIMIT

1000

Default query limit

is.codion.framework.model.EntityTableModel.CONDITION_REQUIRED

false

Whether queries require at least one condition

is.codion.framework.model.EntityQueryModel.SELECT_ATTRIBUTES

empty

Default attributes to exclude from queries

6. EntitySearchModel

The EntitySearchModel is the model component underlying the EntitySearchField UI component. It provides entity search functionality with support for multi-column text searching and entity selection.

6.1. Overview

EntitySearchModel provides:

  • Multi-column text searching with configurable wildcards

  • Single or multi-entity selection management

  • Result limiting to prevent excessive data retrieval

  • Case-sensitive or insensitive search options

  • The model component for EntitySearchField UI component

  • Automatic updates when entities are modified

Basic search model
    EntitySearchModel searchModel = EntitySearchModel.builder(Customer.TYPE, connectionProvider)
            .searchColumns(List.of(Customer.FIRSTNAME, Customer.LASTNAME, Customer.EMAIL))
            .limit(50)
            .build();

    // Perform search
    searchModel.condition().set(() -> Customer.FIRSTNAME.equalTo("john"));

    // Get search result
    List<Entity> result = searchModel.search().result();

6.2. Search Configuration

6.2.1. Search Settings

Configure search behavior per column:

Search settings configuration
    EntitySearchModel searchModel = EntitySearchModel.builder(Customer.TYPE, connectionProvider)
            .searchColumns(List.of(Customer.FIRSTNAME, Customer.LASTNAME))
            .build();

    // Get settings for a specific column
    EntitySearchModel.Settings settings = searchModel.settings().get(Customer.LASTNAME);

    // Add wildcards automatically
    settings.wildcardPrefix().set(true);   // Adds % before search text
    settings.wildcardPostfix().set(true);  // Adds % after search text

    // Replace spaces with wildcards
    settings.spaceAsWildcard().set(true);  // "john smith" → "john%smith"

    // Case sensitivity
    settings.caseSensitive().set(false);   // Case-insensitive search

6.2.2. Wildcard Strategies

The search model supports different wildcard configurations:

  • Prefix search (autocomplete style): wildcardPrefix(false), wildcardPostfix(true) - "joh" → "joh%"

  • Contains search: wildcardPrefix(true), wildcardPostfix(true) - "ohn" → "%ohn%"

  • Exact search: wildcardPrefix(false), wildcardPostfix(false) - "john" → "john"

  • Multi-word search: spaceAsWildcard(true) - "john reyk" → "%john%reyk%"

6.3. Selection Management

6.3.1. Single Selection Mode

For selecting one entity at a time:

Single selection search model
    EntitySearchModel searchModel = EntitySearchModel.builder(Album.TYPE, connectionProvider)
            .searchColumns(List.of(Album.TITLE))
            .build();

    // Set selection programmatically
    Entity album = getAlbum(connectionProvider);
    searchModel.selection().entity().set(album);

    // React to selection changes
    searchModel.selection().entity().addConsumer(selectedAlbum -> {
      if (selectedAlbum != null) {
        displayAlbumDetails(selectedAlbum);
      }
    });

    // Clear selection
    searchModel.selection().clear();

6.3.2. Multi-Selection Mode

For selecting multiple entities:

Multi-selection search model
    EntitySearchModel searchModel = EntitySearchModel.builder(Track.TYPE, connectionProvider)
            .searchColumns(List.of(Track.NAME))
            .build();

    // Get all selected entities
    Collection<Entity> selectedTracks = searchModel.selection().entities().get();

    // Add to selection
    Entity track = getTrack(connectionProvider);
    searchModel.selection().entities().add(track);

    // Remove from selection
    searchModel.selection().entities().remove(track);

    // Replace entire selection
    searchModel.selection().entities().set(List.of(track));

6.4. Configuration Properties

Table 2. EntitySearchModel Configuration
Property Default Description

is.codion.framework.model.EntitySearchModel.HANDLE_EDIT_EVENTS

true

Whether search models react to entity edit events

is.codion.framework.model.EntitySearchModel.DEFAULT_LIMIT

100

Default result limit for search models

is.codion.framework.model.EntitySearchModel.WILDCARD_PREFIX

false

Default wildcard prefix setting

is.codion.framework.model.EntitySearchModel.WILDCARD_POSTFIX

true

Default wildcard postfix setting

is.codion.framework.model.EntitySearchModel.SPACE_AS_WILDCARD

false

Default space replacement setting

is.codion.framework.model.EntitySearchModel.CASE_SENSITIVE

false

Default case sensitivity setting

7. Model Linking

Model linking provides the mechanism for establishing master-detail relationships between entity models. The framework automatically synchronizes detail models based on master model selection and data changes.

7.1. Overview

The ModelLink API enables automatic detail model filtering based on master selection and propagation of data changes.

    // Invoice -> InvoiceLines
    SwingEntityModel invoiceModel = new SwingEntityModel(Invoice.TYPE, connectionProvider);
    SwingEntityModel invoiceLineModel = new SwingEntityModel(InvoiceLine.TYPE, connectionProvider);

    invoiceModel.detailModels().add(invoiceLineModel);

    // Configure detail model for optimal performance
    invoiceLineModel.tableModel().queryModel().conditionRequired().set(true); // Don't load all lines
    invoiceLineModel.tableModel().queryModel().limit().set(1000); // Reasonable limit

Create links with specific behavior:

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityModel invoiceModel = new SwingEntityModel(Invoice.TYPE, connectionProvider);

    ModelLink<SwingEntityModel, SwingEntityEditModel, SwingEntityTableModel> customLink =
            customerModel.link(invoiceModel)
                    .active(true)
                    .onSelection(selectedCustomers -> {
                      // Custom selection logic
                      if (selectedCustomers.size() > 1) {
                        // Handle multi-selection differently
                        invoiceModel.tableModel().queryModel().condition().clear();
                        invoiceModel.tableModel().queryModel().where().set(() ->
                                Invoice.CUSTOMER_FK.in(selectedCustomers)
                        );
                      }
                    })
                    .build();

    customerModel.detailModels().add(customLink);

7.3. Automatic Foreign Key Management

The ForeignKeyModelLink specializes ModelLink for foreign key relationships:

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityModel invoiceModel = new SwingEntityModel(Invoice.TYPE, connectionProvider);

    // ForeignKeyModelLink is created automatically when foreign key is detected
    customerModel.detailModels().add(invoiceModel);

    // Or configure explicitly
    customerModel.detailModels().add(ForeignKeyModelLink.builder(invoiceModel, Invoice.CUSTOMER_FK)
            // Clear foreign key value when master has no selection
            .clearValueOnEmptySelection(true)
            // Set foreign key value automatically on insert
            .setValueOnInsert(true)
            // Control when to refresh detail data
            .refreshOnSelection(true)
            // Filter detail records based on master selection
            .setConditionOnInsert(true)
            .build());

7.4. Simple One-to-Many

Classic master-detail relationship:

    // Invoice -> InvoiceLines
    SwingEntityModel invoiceModel = new SwingEntityModel(Invoice.TYPE, connectionProvider);
    SwingEntityModel invoiceLineModel = new SwingEntityModel(InvoiceLine.TYPE, connectionProvider);

    invoiceModel.detailModels().add(invoiceLineModel);

    // Configure detail model for optimal performance
    invoiceLineModel.tableModel().queryModel().conditionRequired().set(true); // Don't load all lines
    invoiceLineModel.tableModel().queryModel().limit().set(1000); // Reasonable limit

7.5. Multi-Level Hierarchy

Deep master-detail chains:

    // Customer -> Invoice -> InvoiceLine
    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityModel invoiceModel = new SwingEntityModel(Invoice.TYPE, connectionProvider);
    SwingEntityModel invoiceLineModel = new SwingEntityModel(InvoiceLine.TYPE, connectionProvider);

    // Build hierarchy
    customerModel.detailModels().add(invoiceModel);
    invoiceModel.detailModels().add(invoiceLineModel);

    // Configure each level
    invoiceModel.tableModel().queryModel().conditionRequired().set(true);
    invoiceLineModel.tableModel().queryModel().conditionRequired().set(true);

    // Selection cascades down the hierarchy automatically
    Entity customer = getCustomer(connectionProvider);
    customerModel.tableModel().selection().item().set(customer);
    // Invoices for customer are loaded
    // When an invoice is selected, its lines are loaded
    invoiceModel.tableModel().selection().indexes().increment();// selects first

8. EntityApplicationModel

entity application model diagram

The EntityApplicationModel class serves as the base for the application. Its main purpose is to hold references to the root EntityModel instances used by the application.

When extending this class you must provide a constructor with a single EntityConnectionProvider parameter, as seen below.

public class StoreApplicationModel extends SwingEntityApplicationModel {

  public StoreApplicationModel(EntityConnectionProvider connectionProvider) {
    super(connectionProvider, List.of(createCustomerModel(connectionProvider)));
  }

  private static SwingEntityModel createCustomerModel(EntityConnectionProvider connectionProvider) {
    CustomerModel customerModel =
            new CustomerModel(connectionProvider);
    CustomerAddressModel customerAddressModel =
            new CustomerAddressModel(connectionProvider);

    customerModel.detailModels().add(customerAddressModel);

    //populate the model with rows from the database
    customerModel.tableModel().items().refresh();

    return customerModel;
  }
}

9. Application load testing

The application load testing harness is used to see how your application, server and database handle multiple concurrent users.

This is done by using the LoadTestModel and LoadTestPanel classes as shown below.

public class StoreLoadTest {

  private static final class StoreApplicationModelFactory
          implements Function<User, StoreApplicationModel> {

    @Override
    public StoreApplicationModel apply(User user) {
      EntityConnectionProvider connectionProvider =
              RemoteEntityConnectionProvider.builder()
                      .user(user)
                      .domainType(Store.DOMAIN)
                      .build();

      return new StoreApplicationModel(connectionProvider);
    }
  }

  private static class StoreScenarioPerformer
          implements Performer<StoreApplicationModel> {

    private static final Random RANDOM = new Random();

    @Override
    public void perform(StoreApplicationModel application) {
      SwingEntityModel customerModel = application.entityModels().get(Customer.TYPE);
      customerModel.tableModel().items().refresh();
      selectRandomRow(customerModel.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<StoreApplicationModel> loadTest =
            LoadTest.builder(new StoreApplicationModelFactory(),
                            application -> application.connectionProvider().close())
                    .user(User.parse("scott:tiger"))
                    .scenarios(List.of(scenario(new StoreScenarioPerformer())))
                    .name("Store LoadTest - " + EntityConnectionProvider.CLIENT_CONNECTION_TYPE.get())
                    .build();
    loadTestPanel(loadTestModel(loadTest)).run();
  }
}