1. EntityPanel

entity panel diagram

The EntityPanel is the base UI class for working with entity instances. It usually consists of an EntityTablePanel, an EntityEditPanel, and a set of detail panels representing the entities having a master/detail relationship with the underlying entity.

1.1. Basics

You can either extend the EntityPanel class or instantiate one directly, depending on your needs.

public class AddressPanel extends EntityPanel {

  public AddressPanel(SwingEntityModel addressModel) {
    super(addressModel, new AddressEditPanel(addressModel.editModel()));
  }
}
SwingEntityModel addressModel =
        new SwingEntityModel(Address.TYPE, connectionProvider);

EntityPanel addressPanel =
        new EntityPanel(addressModel,
                new AddressEditPanel(addressModel.editModel()));

1.2. Detail panels

Adding a detail panel is done with a single method call, but note that the underlying EntityModel must contain the correct detail model for the detail panel, in this case a CustomerModel instance, see detail models. See EntityApplicationPanel.

public class CustomerPanel extends EntityPanel {

  public CustomerPanel(SwingEntityModel entityModel) {
    super(entityModel);
    SwingEntityModel addressModel =
            entityModel.detailModels().get(Address.TYPE);
    detailPanels().add(new AddressPanel(addressModel));
  }
}

2. EntityEditPanel

The EntityEditPanel manages the input components (text fields, combo boxes and such) for editing an entity instance.

When extending an EntityEditPanel you must implement the initializeUI() method, which initializes the edit panel UI. The EntityEditPanel class exposes methods for creating input components and binding them with the underlying EntityEditModel instance.

public class CustomerEditPanel extends EntityEditPanel {

  public CustomerEditPanel(SwingEntityEditModel editModel) {
    super(editModel);
  }

  @Override
  protected void initializeUI() {
    //the firstName field should receive the focus whenever the panel is initialized
    focus().initial().set(Customer.FIRST_NAME);

    createTextField(Customer.FIRST_NAME);
    createTextField(Customer.LAST_NAME);
    createTextField(Customer.EMAIL);
    createCheckBox(Customer.ACTIVE);

    setLayout(new GridLayout(4, 1));
    //the addInputPanel method creates and adds a panel containing the
    //component associated with the attribute as well as a JLabel with the
    //property caption as defined in the domain model
    addInputPanel(Customer.FIRST_NAME);
    addInputPanel(Customer.LAST_NAME);
    addInputPanel(Customer.EMAIL);
    addInputPanel(Customer.ACTIVE);
  }
}
public class AddressEditPanel extends EntityEditPanel {

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

  @Override
  protected void initializeUI() {
    focus().initial().set(Address.STREET);

    createTextField(Address.STREET).columns(25);
    createTextField(Address.CITY).columns(25);
    createCheckBox(Address.VALID);

    setLayout(new GridLayout(3, 1, 5, 5));
    addInputPanel(Address.STREET);
    addInputPanel(Address.CITY);
    addInputPanel(Address.VALID);
  }
}
public class CustomerAddressEditPanel extends EntityEditPanel {

  public CustomerAddressEditPanel(SwingEntityEditModel editModel) {
    super(editModel);
  }

  @Override
  protected void initializeUI() {
    focus().initial().set(CustomerAddress.ADDRESS_FK);

    createComboBoxPanel(CustomerAddress.ADDRESS_FK, this::createAddressEditPanel)
            .preferredWidth(280)
            .includeAddButton(true);

    setLayout(borderLayout());

    addInputPanel(CustomerAddress.ADDRESS_FK);
  }

  private AddressEditPanel createAddressEditPanel() {
    return new AddressEditPanel(new SwingEntityEditModel(Address.TYPE, editModel().connectionProvider()));
  }
}

2.1. Detailed example

Here’s how a text field is created and added to the edit panel.

createTextField(Customer.FIRST_NAME)
        .columns(12);

setLayout(gridLayout(1, 1));
addInputPanel(Customer.FIRST_NAME);

And here’s the equivilent code, showing what’s going on behind the scenes.

ColumnDefinition<String> firstNameDefinition =
        editModel().entityDefinition().columns().definition(Customer.FIRST_NAME);

//create the text field
JTextField firstNameField = new JTextField();
firstNameField.setColumns(12);
firstNameDefinition.description()
        .ifPresent(firstNameField::setToolTipText);
//associate the text field with the first name attribute
component(Customer.FIRST_NAME).set(firstNameField);

//wrap the text field in a ComponentValue
ComponentValue<String, JTextField> firstNameFieldValue =
        new AbstractTextComponentValue<String, JTextField>(firstNameField) {
          @Override
          protected String getComponentValue() {
            return component().getText();
          }

          @Override
          protected void setComponentValue(String text) {
            component().setText(text);
          }
        };

//link the component value to the attribute value in the editor
firstNameFieldValue.link(editModel().editor().value(Customer.FIRST_NAME));

//create the first name label
JLabel firstNameLabel = new JLabel(firstNameDefinition.caption());
//associate the label with the text field
firstNameLabel.setLabelFor(firstNameField);

//create an input panel, with the label and text field
JPanel firstNamePanel = new JPanel(borderLayout());
firstNamePanel.add(firstNameLabel, BorderLayout.NORTH);
firstNamePanel.add(firstNameField, BorderLayout.CENTER);

setLayout(gridLayout(1, 1));
add(firstNamePanel);

2.2. Input controls

2.2.1. Boolean

JCheckBox checkBox =
        createCheckBox(Demo.BOOLEAN)
                .build();

NullableCheckBox nullableCheckBox =
        (NullableCheckBox) createCheckBox(Demo.BOOLEAN)
                .nullable(true)
                .build();

JComboBox<Item<Boolean>> comboBox =
        createBooleanComboBox(Demo.BOOLEAN)
                .build();

2.2.2. Foreign key

EntityComboBox comboBox =
        createComboBox(Demo.FOREIGN_KEY)
                .build();

EntitySearchField searchField =
        createSearchField(Demo.FOREIGN_KEY)
                .build();

//readOnly
JTextField textField =
        createTextField(Demo.FOREIGN_KEY)
                .build();

2.2.3. Temporal

TemporalField<LocalDateTime> textField =
        (TemporalField<LocalDateTime>) createTextField(Demo.LOCAL_DATE)
                .build();

TemporalField<LocalDate> localDateField =
        createTemporalField(Demo.LOCAL_DATE)
                .build();

TemporalFieldPanel<LocalDate> temporalPanel =
        createTemporalFieldPanel(Demo.LOCAL_DATE)
                .build();

2.2.4. Numerical

NumberField<Integer> integerField =
        (NumberField<Integer>) createTextField(Demo.INTEGER)
                .build();

integerField =
        createIntegerField(Demo.INTEGER)
                .build();

NumberField<Long> longField =
        (NumberField<Long>) createTextField(Demo.LONG)
                .build();

longField =
        createLongField(Demo.LONG)
                .build();

NumberField<Double> doubleField =
        (NumberField<Double>) createTextField(Demo.DOUBLE)
                .build();

doubleField =
        createDoubleField(Demo.DOUBLE)
                .build();

NumberField<BigDecimal> bigDecimalField =
        (NumberField<BigDecimal>) createTextField(Demo.BIG_DECIMAL)
                .build();

bigDecimalField =
        createBigDecimalField(Demo.BIG_DECIMAL)
                .build();

2.2.5. Text

JTextField textField =
        createTextField(Demo.TEXT)
                .build();

JFormattedTextField maskedField =
        createMaskedTextField(Demo.FORMATTED_TEXT)
                .mask("###:###")
                .valueContainsLiteralCharacters(true)
                .build();

JTextArea textArea =
        createTextArea(Demo.LONG_TEXT)
                .rowsColumns(5, 20)
                .build();

TextFieldPanel inputPanel =
        createTextFieldPanel(Demo.LONG_TEXT)
                .build();

2.2.6. Selection

DefaultComboBoxModel<String> comboBoxModel =
        new DefaultComboBoxModel<>(new String[] {"One", "Two"});

JComboBox<String> comboBox =
        createComboBox(Demo.TEXT, comboBoxModel)
                .editable(true)
                .build();

2.2.7. Items

JComboBox<Item<String>> comboBox =
        createItemComboBox(Demo.ITEM_LIST)
                .build();

2.3. Panels & labels

JLabel label = createLabel(Demo.TEXT)
        .build();

JPanel inputPanel = createInputPanel(Demo.TEXT)
        .label(new JLabel("Label"))
        .build();

2.4. Advanced Patterns

2.4.1. Configuration Options

EntityEditPanel supports configuration via a lambda in the constructor:

    class InvoiceEditPanel extends EntityEditPanel {
      public InvoiceEditPanel(SwingEntityEditModel editModel) {
        super(editModel, config ->
                // Keep displaying newly inserted invoice since we'll continue
                // working with it by adding invoice lines
                config.clearAfterInsert(false));
      }

      @Override
      protected void initializeUI() {
        // UI setup
      }
    }

2.4.2. Focus Management

Configure the focus behaviour:

    class CustomerEditPanel extends EntityEditPanel {
      public CustomerEditPanel(SwingEntityEditModel editModel) {
        super(editModel);
      }

      @Override
      protected void initializeUI() {
        focus().initial().set(Customer.FIRSTNAME);
        focus().afterInsert().set(Customer.ADDRESS);

        // Create your input components...
      }
    }

2.4.3. Inline Edit Panels with ComboBoxPanel

Create combo boxes with inline add/edit capabilities:

    class TrackEditPanel extends EntityEditPanel {
      public TrackEditPanel(SwingEntityEditModel editModel) {
        super(editModel);
      }

      @Override
      protected void initializeUI() {
        createComboBoxPanel(Track.MEDIATYPE_FK, this::createMediaTypeEditPanel)
                .preferredWidth(160)
                .includeAddButton(true)
                .includeEditButton(true);

        createSearchFieldPanel(Track.MEDIATYPE_FK, this::createMediaTypeEditPanel)
                .preferredWidth(160)
                .includeAddButton(true)
                .includeEditButton(true);
      }

      private EntityEditPanel createMediaTypeEditPanel() {
        return new MediaTypeEditPanel(new SwingEntityEditModel(Chinook.MediaType.TYPE, editModel().connectionProvider()));
      }
    }

2.4.4. Custom Component Integration

Replace default components with custom ones:

    class TrackEditPanel extends EntityEditPanel {
      public TrackEditPanel(SwingEntityEditModel editModel) {
        super(editModel);
      }

      @Override
      protected void initializeUI() {
        // Create a custom component
        DurationComponentValue durationValue = createDurationComponent();

        // Link it to the attribute value
        editModel().editor().value(Track.MILLISECONDS).link(durationValue);
        // And set the component it as the attribute component
        component(Track.MILLISECONDS).set(durationValue.component());
      }

      private DurationComponentValue createDurationComponent() {
        // Custom implementation
        return new DurationComponentValue();
      }
    }

2.4.5. Keyboard Shortcuts and Actions

Add custom keyboard shortcuts for enhanced productivity:

    class CustomerEditPanel extends EntityEditPanel {
      public CustomerEditPanel(SwingEntityEditModel editModel) {
        super(editModel);
      }

      @Override
      protected void initializeUI() {
        createTextField(Customer.STATE)
                .keyEvent(KeyEvents.builder()
                        .keyCode(VK_SPACE)
                        .modifiers(CTRL_DOWN_MASK)
                        .action(Control.action(this::selectStateFromExistingValues)));
      }

      private void selectStateFromExistingValues(ActionEvent event) {
        JTextField stateField = (JTextField) event.getSource();

        Dialogs.select()
                .list(editModel().connection().select(Customer.STATE))
                .owner(stateField)
                .select()
                .single()
                .ifPresent(stateField::setText);
      }
    }

2.4.6. Detail Panel Integration

EntityEditPanel can include detail panels for master-detail relationships:

    class InvoiceEditPanel extends EntityEditPanel {
      private final EntityPanel invoiceLinePanel;

      public InvoiceEditPanel(SwingEntityEditModel editModel, SwingEntityModel invoiceLineModel) {
        super(editModel, config -> config.clearAfterInsert(false));
        this.invoiceLinePanel = createInvoiceLinePanel(invoiceLineModel);
      }

      @Override
      protected void initializeUI() {
        // Initialize main edit controls...

        // Add detail panel
        add(invoiceLinePanel, BorderLayout.SOUTH);
      }

      private EntityPanel createInvoiceLinePanel(SwingEntityModel invoiceLineModel) {
        // Create and return invoice line panel
        return new EntityPanel(invoiceLineModel);
      }
    }

2.5. Custom actions

The action mechanism used throughout the Codion framework is based on the Control class and its subclasses and the Controls class which represents a collection of controls.

3. EntityTablePanel

The EntityTablePanel provides a table view of entities.

3.1. Adding a print action

The most common place to add a custom control is the table popup menu, i.e. an action for printing reports or for acting on the selected rows. For the simplest case, where a single print action is required, a custom control can be associated with the PRINT ControlKey, this control will appear in the Print submenu in the table popup menu as well as on the table toolbar.

For more complex cases, where multiple print controls are required, custom controls can be associated with the PRINT_CONTROLS ControlKey.

public class CustomerTablePanel extends EntityTablePanel {

  public CustomerTablePanel(SwingEntityTableModel tableModel) {
    super(tableModel);
    // associate a custom Control with the PRINT control key,
    // which calls the viewCustomerReport method in this class,
    // enabled only when the selection is not empty
    control(PRINT).set(Control.builder()
            .command(this::viewCustomerReport)
            .caption("Customer report")
            .smallIcon(FrameworkIcons.instance().print())
            .enabled(tableModel().selection().empty().not())
            .build());
  }

  private void viewCustomerReport() {
    List<Entity> selectedCustomers = tableModel().selection().items().get();
    if (selectedCustomers.isEmpty()) {
      return;
    }

    Collection<String> customerIds = Entity.values(Customer.ID, selectedCustomers);
    Map<String, Object> reportParameters = new HashMap<>();
    reportParameters.put("CUSTOMER_IDS", customerIds);

    JasperPrint customerReport = tableModel().connection()
            .report(Customer.REPORT, reportParameters);

    Dialogs.builder()
            .component(new JRViewer(customerReport))
            .owner(this)
            .modal(false)
            .title("Customer Report")
            .size(new Dimension(800, 600))
            .show();
  }
}

4. EntityPanel.Builder

Use the EntityPanel.Builder class to specify a EntityPanel class configuration, for panels that should not be initialized until used, such the lookup table panels.

  private static List<EntityPanel.Builder> createLookupPanelBuilders() {
    EntityPanel.Builder addressPanelBuilder = EntityPanel.builder()
            .entityType(Address.TYPE)
            .panel(connectionProvider -> {
              SwingEntityModel addressModel =
                      new SwingEntityModel(Address.TYPE, connectionProvider);

              return new EntityPanel(addressModel,
                      new AddressEditPanel(addressModel.editModel()));
            });

    return List.of(addressPanelBuilder);
  }

5. EntityApplicationPanel

The EntityApplicationPanel class serves as the main application UI. When extending this class you must provide a constructor with a single application model parameter, as seen below.

public class StoreApplicationPanel extends EntityApplicationPanel<StoreApplicationModel> {

  public StoreApplicationPanel(StoreApplicationModel applicationModel) {
    super(applicationModel, createPanels(applicationModel), createLookupPanelBuilders());
  }

  private static List<EntityPanel> createPanels(StoreApplicationModel applicationModel) {
    CustomerModel customerModel = (CustomerModel)
            applicationModel.entityModels().get(Customer.TYPE);
    CustomerAddressModel customerAddressModel = (CustomerAddressModel)
            customerModel.detailModels().get(CustomerAddress.TYPE);

    EntityPanel customerPanel = new EntityPanel(customerModel,
            new CustomerEditPanel(customerModel.editModel()),
            new CustomerTablePanel(customerModel.tableModel()));
    EntityPanel customerAddressPanel = new EntityPanel(customerAddressModel,
            new CustomerAddressEditPanel(customerAddressModel.editModel()));

    customerPanel.detailPanels().add(customerAddressPanel);

    return List.of(customerPanel);
  }

  private static List<EntityPanel.Builder> createLookupPanelBuilders() {
    EntityPanel.Builder addressPanelBuilder = EntityPanel.builder()
            .entityType(Address.TYPE)
            .panel(connectionProvider -> {
              SwingEntityModel addressModel =
                      new SwingEntityModel(Address.TYPE, connectionProvider);

              return new EntityPanel(addressModel,
                      new AddressEditPanel(addressModel.editModel()));
            });

    return List.of(addressPanelBuilder);
  }

  public static void main(String[] args) {
    Locale.setDefault(new Locale("en", "EN"));
    EntityPanel.Config.TOOLBAR_CONTROLS.set(true);
    ReferentialIntegrityErrorHandling.REFERENTIAL_INTEGRITY_ERROR_HANDLING
            .set(ReferentialIntegrityErrorHandling.DISPLAY_DEPENDENCIES);
    EntityApplicationPanel.builder(StoreApplicationModel.class, StoreApplicationPanel.class)
            .domain(Store.DOMAIN)
            .applicationName("Store")
            .defaultUser(User.parse("scott:tiger"))
            .start();
  }
}

6. EntitySearchField

The EntitySearchField is a powerful UI component for entity selection through text-based searching. It extends HintTextField and provides a search interface that triggers on ENTER key, displaying results based on the configured search criteria.

6.1. Overview

EntitySearchField provides:

  • Text-based entity searching with automatic result handling

  • Single or multi-entity selection

  • Customizable result selection UI (list, table, or custom)

  • Optional add/edit functionality for creating or modifying entities

  • Search progress indication (wait cursor or progress bar)

  • Automatic search on focus loss (optional)

  • Keyboard shortcuts for add/edit operations

6.2. Basic Usage

Creating a basic search field
    EntitySearchModel searchModel = EntitySearchModel.builder()
            .entityType(Customer.TYPE)
            .connectionProvider(connectionProvider)
            .searchColumns(List.of(Customer.FIRSTNAME, Customer.EMAIL))
            .build();

    EntitySearchField searchField = EntitySearchField.builder()
            .model(searchModel)
            .multiSelection()
            .columns(20)
            .build();

6.3. Search Behavior

The search field operates as follows:

  1. User types search text and presses ENTER

  2. If the search returns:

    • No results: A message dialog is shown

    • Single result: That entity is automatically selected

    • Multiple results: A selection dialog appears

6.3.1. Single Selection

For selecting one entity at a time:

    EntitySearchField searchField = EntitySearchField.builder()
            .model(searchModel)
            .singleSelection()
            .build();

6.3.2. Multi-Selection

For selecting multiple entities (this is the default mode):

    EntitySearchField searchField = EntitySearchField.builder()
            .model(searchModel)
            .multiSelection()
            .build();

6.4. Customization Options

6.4.1. Custom Selectors

The default selector uses a list for result selection. You can provide custom selectors:

Custom table selector
    EntitySearchField searchField = EntitySearchField.builder()
            .model(searchModel)
            .multiSelection()
            .selectorFactory(new CustomerSelectorFactory())
            .build();

6.4.2. Add and Edit Controls

Enable inline entity creation and editing:

Search field with add/edit capabilities
    SwingEntityEditModel editModel = new SwingEntityEditModel(Customer.TYPE, connectionProvider);

    EntitySearchField searchField = EntitySearchField.builder()
            .model(searchModel)
            .singleSelection()
            .editPanel(() -> new CustomerEditPanel(editModel))
            .confirmAdd(true)     // Confirm before adding
            .confirmEdit(true)    // Confirm before editing
            .build();

    // Access controls
    searchField.addControl();   // INSERT key by default
    searchField.editControl();  // CTRL+INSERT by default

6.4.3. Search Indicators

Configure how search progress is displayed:

Progress bar indicator
    EntitySearchField searchField = EntitySearchField.builder()
            .model(searchModel)
            .multiSelection()
            .searchIndicator(SearchIndicator.PROGRESS_BAR)
            .build();

6.4.4. Field Configuration

Various field configurations
    EntitySearchField searchField = EntitySearchField.builder()
            .model(searchModel)
            .singleSelection()
            .columns(20)                      // Field width
            .upperCase(true)                  // Force uppercase
            .searchHintEnabled(true)          // Show "Search..." hint
            .searchOnFocusLost(true)          // Auto-search when focus lost
            .selectionToolTip(true)           // Show selection as tooltip
            .editable(false)                  // Make read-only
            .stringFactory(entity ->    // Custom display text
                    entity.get(Customer.LASTNAME) + " - " + entity.get(Customer.CITY))
            .separator(" | ")                 // Multi-selection separator
            .build();

6.5. Search Control

You can trigger searches programmatically:

Programmatic search control
    EntitySearchField searchField = EntitySearchField.builder()
            .model(searchModel)
            .multiSelection()
            .build();

    // Get search control
    Control searchControl = searchField.searchControl();

    // Use in toolbar or menu
    Controls.builder()
            .control(searchControl)
            .build();

6.6. Advanced Features

6.6.1. Component Value Integration

A EntitySearchField based ComponentValue can be created via buildValue():

Reactive search field
    SwingEntityEditModel editModel = new SwingEntityEditModel(Invoice.TYPE, connectionProvider);

    ComponentValue<Entity, EntitySearchField> searchFieldValue =
            EntitySearchField.builder()
                    .model(searchModel)
                    .singleSelection()
                    .buildValue();

    EntitySearchField searchField = searchFieldValue.component();
    // React to selection changes
    searchField.model().selection().entities().addConsumer(selectedEntities ->
            System.out.println("Selected: " + selectedEntities));

    // Link to edit model
    editModel.editor().value(Invoice.CUSTOMER_FK).link(searchFieldValue);

6.6.2. Custom Edit Component Factory

Use custom search fields in edit panels:

Custom factory example from Chinook demo
class TrackEditComponentFactory extends DefaultEditComponentFactory<Entity, EntitySearchField> {

    @Override
    protected EntitySearchField.SingleSelectionBuilder searchField(
            ForeignKey foreignKey,
            EntityDefinition entityDefinition,
            EntitySearchModel searchModel) {
        return super.searchField(foreignKey, entityDefinition, searchModel)
                .selectorFactory(new TrackSelectorFactory());
    }
}

6.7. Configuration Properties

Table 1. EntitySearchField Configuration
Property Default Description

is.codion.swing.framework.ui.component.EntitySearchField.searchIndicator

WAIT_CURSOR

How to indicate ongoing searches (WAIT_CURSOR or PROGRESS_BAR)

6.8. Best Practices

  1. Provide Clear Search Columns: Configure the search model with appropriate searchable columns

  2. Consider Performance: Use result limits in the search model for large datasets

  3. Keyboard Support: Leverage the built-in keyboard shortcuts (INSERT for add, CTRL+INSERT for edit)

  4. Custom Selectors: Create custom selectors for complex selection scenarios

7. Reporting with JasperReports

Codion uses a plugin oriented approach to report viewing and provides an implementation for JasperReports.

With the Codion JasperReports plugin you can either design your report based on an SQL query in which case you use the JRReport class, which facilitates the report being filled using the active database connection, or you can design your report around the JRDataSource implementation provided by the JasperReportsDataSource class, which is constructed around an iterator.

7.1. JDBC Reports

Using a report based on an SQL query is the simplest way of viewing a report using Codion, just add a method similar to the one below to a EntityTablePanel subclass. You can then create an action calling that method and put it in for example the table popup menu as described in the adding a print action section.

public class CustomerTablePanel extends EntityTablePanel {

  public CustomerTablePanel(SwingEntityTableModel tableModel) {
    super(tableModel);
    // associate a custom Control with the PRINT control key,
    // which calls the viewCustomerReport method in this class,
    // enabled only when the selection is not empty
    control(PRINT).set(Control.builder()
            .command(this::viewCustomerReport)
            .caption("Customer report")
            .smallIcon(FrameworkIcons.instance().print())
            .enabled(tableModel().selection().empty().not())
            .build());
  }

  private void viewCustomerReport() {
    List<Entity> selectedCustomers = tableModel().selection().items().get();
    if (selectedCustomers.isEmpty()) {
      return;
    }

    Collection<String> customerIds = Entity.values(Customer.ID, selectedCustomers);
    Map<String, Object> reportParameters = new HashMap<>();
    reportParameters.put("CUSTOMER_IDS", customerIds);

    JasperPrint customerReport = tableModel().connection()
            .report(Customer.REPORT, reportParameters);

    Dialogs.builder()
            .component(new JRViewer(customerReport))
            .owner(this)
            .modal(false)
            .title("Customer Report")
            .size(new Dimension(800, 600))
            .show();
  }
}

7.2. JRDataSource Reports

The JRDataSource implementation provided by the JasperReportsDataSource simply iterates through the iterator received via the constructor and retrieves the field values from the underlying entities. The easiest way to make this work is to design the report using field names that correspond to the attribute names, so using the Store domain example from above the fields in a report showing the available items would have to be named 'name', 'active', 'category_code' etc.

EntityConnection connection = connectionProvider.connection();

EntityDefinition customerDefinition =
        connection.entities().definition(Customer.TYPE);

Iterator<Entity> customerIterator =
        connection.select(all(Customer.TYPE)).iterator();

JasperReportsDataSource<Entity> dataSource =
        new JasperReportsDataSource<>(customerIterator,
                (entity, reportField) ->
                        entity.get(customerDefinition.attributes().getOrThrow(reportField.getName())));

JRReport customerReport = fileReport("reports/customer.jasper");

JasperPrint jasperPrint = JasperReports.fillReport(customerReport, dataSource);