JMinor Application Framework

As simple as possible but not simpler

User Tools

Site Tools


JMinor Manual

The basics

Common classes

Two common classes used throughout the framework are the Event and State classes and their respective observers EventObserver and StateObserver and listeners EventListener and EventInfoListener.


The Event class is a simple event implementation used throughout the framework to signal object state changes or to indicate that an action is about to start or has ended. Classes typically publish their events via public addListener methods. Events are triggered by calling the fire() method, with or without an eventInfo parameter.

Events are instantiated via the Events factory class.

To listen to Events you use the EventListener or EventDataListener interfaces.

Event<String> event = Events.event();
event.addDataListener(new EventDataListener<String>() {
  public void eventOccurred(final String data) {
    System.out.println("Event happened: " + data);
});;//writes out "Event happened: null";"data");//writes out "Event happened: data";


The State class encapsulates a boolean state (active/inactive) and provides a StateObserver:

States are instantiated via the States factory class.

State state = States.state();
state.addListener(new EventListener() {
  public void eventOccurred() {
    System.out.println("State changed");
state.setActive(true);//writes out "State changed";

Any Action object can be linked to a State object via the UiUtil.linkToEnabledState method, where the action's enabled status is synchronized with the state.

State state = States.state();
Action action = new AbstractAction("action") {
  public void actionPerformed(final ActionEvent e) {}
UiUtil.linkToEnabledState(state, action);
//action.isEnabled() now returns false, since State objects are by default inactive.
//action.isEnabled() now returns true.

Domain model


The domain model is based around two interfaces, org.jminor.framework.domain.Entity, which represents a row in a table, and org.jminor.framework.domain.Property, which represents columns. The org.jminor.framework.domain.Domain class serves as a container for the domain model definition and as a factory for Entity and Entity.Key objects.


An Entity object represents a row in a table and maps each column value to its respective property. Each type of Entity is identified by a entityId, a string constant which must be unique within the domain. The simplest way to define a unique entityId is to use the underlying table name.

Defining the entityId as a static string constant in the domain class is recommended.

 * The Store class defines the domain model for the Store application
public class Store extends Domain {
  //the full name of the table, will serve as the entityId for the Entity based on this table
  public static final String T_ADDRESS = "store.address";
  //the full name of the table, will serve as the entityId for the Entity based on this table
  public static final String T_CUSTOMER = "store.customer";


Each column in a table is defined by the Property class or one of its subclasses. The Properties class provides factory methods for constructing Property objects. The following properties can be set by using chained method calls: hidden, updatable, readOnly, nullable, defaultValue, maxLength, preferredWidth, description, mnemonic, columnHasDefaultValue, format

Properties.columnProperty(PROPERTY_ID, Types.INTEGER, "Caption").setNullable(false).setDescription("This is a non-nullable property")

Supported data types

JMinor supports the following column data types:

  • Integer (java.sql.Types.INTEGER)
  • Double (java.sql.Types.DOUBLE)
  • Long (java.sql.Types.BIGINT)
  • LocalDateTime (java.sql.Types.TIMESTAMP)
  • LocalDate (java.sql.Types.DATE)
  • LocalTime (java.sql.Types.TIME)
  • String (java.sql.Types.VARCHAR)
  • Boolean (java.sql.Types.BOOLEAN)
  • Character (java.sql.Types.CHAR)
  • Blob (java.sql.Types.BLOB)

Property Id constants

For each column you define a string constant containing the column name, i.e. 'first_name'. This constant is used when defining the property based on that column and serves as the property identifier. This propertyId is used as column name when constructing SQL queries unless the column name is set via setColumnName().

 * The Store class defines the domain model for the Store application
public class Store extends Domain {
  //the full name of the table, will serve as the entityId for the Entity based on this table
  public static final String T_ADDRESS = "store.address";
  //Property constant identifying the column ID in the ADDRESS table
  public static final String ADDRESS_ID = "id";
  //Property constant identifying the column STREET in the ADDRESS table
  public static final String ADDRESS_STREET = "street";
  //Property constant identifying the column CITY in the ADDRESS table
  public static final String ADDRESS_CITY = "city";
  //the full name of the table, will serve as the entityId for the Entity based on this table
  public static final String T_CUSTOMER = "store.customer";
  //Property constant identifying the column ID in the CUSTOMER table
  public static final String CUSTOMER_ID = "id";
  //Property constant identifying the column FIRST_NAME in the CUSTOMER table
  public static final String CUSTOMER_FIRST_NAME = "first_name";
  //Property constant identifying the column LAST_NAME in the CUSTOMER table
  public static final String CUSTOMER_LAST_NAME = "last_name";
  //Property constant identifying the foreign key referencing the ADDRESS entity,
  //the value is somewhat arbitrary since it does not map to a column
  public static final String CUSTOMER_ADDRESS_FK = "address_fk";
  //Property constant identifying the column referencing the STORE.ADDRESS table
  public static final String CUSTOMER_ADDRESS_ID = "address_id";
  //Property constant identifying the column IS_ACTIVE in the CUSTOMER table
  public static final String CUSTOMER_IS_ACTIVE = "is_active";
  //Property constant identifying the denormalized column CITY in the CUSTOMER table
  public static final String CUSTOMER_CITY = "city";
  //Property constant identifying a derived property CUSTOMER entity
  public static final String CUSTOMER_DERIVED = "derived";


Property and its subclasses is used to represent entity properties, these can be transient or based on table columns.

Properties.transientProperty(CUSTOMER_TOKEN, Types.VARCHAR, "Token")


ColumnProperty is used to represent properties that are based on table columns.

Properties.columnProperty(CUSTOMER_LAST_NAME, Types.VARCHAR, "Last name")
Primary key

Entities must have at least one primary key column property.

The only requirement is that the primary key properties represent a unique column combination for the underlying table, it does not have to correspond to an actual table primary key, although that is of course preferable. The framework does not enforce uniqueness for these properties, so a unique or primary key on the corresponding table columns is strongly recommended.

Properties.primaryKeyProperty(CUSTOMER_ID)//by default Types.INTEGER and primaryKeyIndex 0

If the primary key is comprised of more than one column you must set the primary key index.

Properties.columnProperty(ID_1, Types.INTEGER).setPrimaryKeyIndex(0),
Properties.columnProperty(ID_2, Types.INTEGER).setPrimaryKeyIndex(1),


ForeignKeyProperty is a wrapper property used to indicate a foreign key relation. These foreign keys refer to the primary key of the referenced entity and must be constructed accordingly in case of non-trivial primary keys.

//referring to an entity with a single column primary key
Properties.foreignKeyProperty(CUSTOMER_ADDRESS_FK, "Address", T_ADDRESS,
//referring to an entity with a dual column primary key
Properties.foreignKeyProperty(MASTER_FK, "Master", T_MASTER,
        new Property.ColumnProperty[] {

In this example CUSTOMER_ADDRESS_FK is the ID of the foreign key property and can be used to retrieve the actual entity being referred to.

Entity address = customer.getForeignKey(CUSTOMER_ADDRESS_FK);

CUSTOMER_ADDRESS_ID is the actual column used as foreign key and retrieving that will simply return the reference id value.

Integer addressId = customer.getInteger(CUSTOMER_ADDRESS_ID);

By default one level of foreign key values is eagerly fetched during selects, this can be overridden via setFetchDepth(). Note that the example below does not really make sense since the ADDRESS entity doesn't have any foreign keys, but if id did the entities referred to via these keys would be eagerly loaded.

//referring to an entity with a single column primary key
Properties.foreignKeyProperty(CUSTOMER_ADDRESS_FK, "Address", T_ADDRESS,

Boolean Properties

For databases supporting Types.BOOLEAN you simply use Properties.columnProperty.

Properties.columnProperty(CUSTOMER_IS_ACTIVE, Types.BOOLEAN, "Is active")

For databases lacking native boolean support we use the Properties.booleanProperty method, specifying the actual true/false values.

Properties.booleanProperty(CUSTOMER_IS_ACTIVE, Types.INTEGER, "Is active", 1, 0)

If the configuration parameter Database.DATABASE_TYPE has been set prior to calling Properties.booleanProperty and you skip specifying the true/false values, then the database implementation will supply the values typically used by the underlying DBMS, such as 1=true and 0=false for Oracle.

Properties.booleanProperty(CUSTOMER_IS_ACTIVE, Types.INTEGER, "Is active")

For boolean columns using unconventional types you can specify the true and false values.

Properties.booleanProperty(CUSTOMER_IS_ACTIVE, Types.VARCHAR, "Is active", "true", "false")
Properties.booleanProperty(CUSTOMER_IS_ACTIVE, Types.CHAR, "Is active", 'T', 'F')

Note that boolean properties always use the boolean Java type, the framework handles translating to and from the actual column values.

entity.put(CUSTOMER_IS_ACTIVE, true);
boolean isActive = entity.getBoolean(CUSTOMER_IS_ACTIVE);


DenormalizedProperty is used for columns that should automatically get their value from a column in a referenced table. This property automatically gets the value from the column in the referenced table when the corresponding reference property value is set.

Properties.denormalizedProperty(CUSTOMER_CITY, CUSTOMER_ADDRESS_FK,
        getProperty(T_ADDRESS, ADDRESS_CITY), "City")

The property is not kept in sync if the denormalized property is updated in the referenced entity.

Domain domain = getDomain();
Entity address = domain.entity(T_ADDRESS);
address.put(ADDRESS_CITY, "Syracuse");
Entity customer = domain.entity(T_CUSTOMER);
customer.put(CUSTOMER_ADDRESS_FK, address);
customer.get(CUSTOMER_CITY);//returns "Syracuse"
address.put(ADDRESS_CITY, "Canastota");
customer.get(CUSTOMER_CITY, still returns "Syracuse"
customer.put(CUSTOMER_ADDRESS_FK, address);//set the referenced value again
customer.get(CUSTOMER_CITY);//now this returns "Canastota"


SubqueryProperty is used to represent properties which get their value from a subquery returning a single value. Note that in the example below referenceId must be available when the query is run, that is, the entity must include that column as a property.

Properties.subqueryProperty(SUBQUERY_PROPERTY_ID, Types.VARCHAR, "Caption",
        "select field from schema.table where id = reference_id"))


TransientProperty is used to represent a property which has no underlying column, these properties all have a default value of null and can set and retrieved just like normal properties.


DerivedProperty is used to represent a transient property which value is derived from one or more linked property values. The value of a derived property is provided via a DerivedProperty.Provider implementation as shown below.

Properties.derivedProperty(PROPERTY_ID, Types.INTEGER, "Derived value",
         new Property.DerivedProperty.Provider() {
           public Object getValue(final Map<String, Object> linkedValues) {
             //linkedValues contains the values of the linked properties
             final Integer linkedOne = (Integer) linkedValues.get(SOURCE_PROPERTY_ID_1);
             final Integer linkedTwo = (Integer) linkedValues.get(SOURCE_PROPERTY_ID_2);
             return linkedOne + linkedTwo;

Example: Chinook domain


The Domain class serves as a factory class for Entity objects as well as a central repository of entity meta-information (the domain model), each entity type must be defined, by calling Domain.define for each entity. By default the framework uses the entityId as table name, unless the tableName parameter is specified.

//public class Store continued
  public Store() {
    //Defining the entity that represents the table STORE.ADDRESS,
    //with a property for each column in the table, identified by their respective constants
        Properties.columnProperty(ADDRESS_STREET, Types.VARCHAR, "Street"),
        Properties.columnProperty(ADDRESS_CITY, Types.VARCHAR, "City"))
        .setStringProvider(new StringProvider<String>(ADDRESS_STREET).addText(" - ").addValue(ADDRESS_CITY)
    //Defining the entity that represents the table STORE.CUSTOMER,
    //with a property for each column in the table, identified by their respective constants
        Properties.columnProperty(CUSTOMER_FIRST_NAME, Types.VARCHAR, "First name").setDescription("The first name of the customer"),
        Properties.columnProperty(CUSTOMER_LAST_NAME, Types.VARCHAR, "Last name").setDescription("The last name of the customer"),
        Properties.foreignKeyProperty(CUSTOMER_ADDRESS_FK, "Address", T_ADDRESS,
        Properties.booleanProperty(CUSTOMER_IS_ACTIVE, Types.VARCHAR, "Is active", "true", "false").setDefaultValue(true),
        Properties.denormalizedProperty(CUSTOMER_CITY, T_ADDRESS,
            getProperty(T_ADDRESS, ADDRESS_CITY), "City"),
        Properties.transientProperty(CUSTOMER_TRANSIENT, Types.VARCHAR, "Transient"))
        .setStringProvider(new StringProvider<String>(CUSTOMER_LAST_NAME).addText(", ").addValue(CUSTOMER_FIRST_NAME)


The Domain.StringProvider class is for providing toString() implementations for each Entity type. This value is f. ex. used when the entity instance is shown in a ComboBox or as a foreign key value in table views.

See the Domain.StringProvider API doc for further information.

Helper classes

The following classes can come in handy when working with entities.


A default Entity.Validator implementation which provides basic range and null validation, can be overridden to provide further validations. Note that this validator is called quite often so it should not perform expensive operations. Validation requiring database access f.ex. belongs in the application model or ui.

define("entityID", [...]).setValidator(new Domain.Validator() {
      public void validate(Entity entity, Property property) throws ValidationException {
        super.validate(entity, property);
        Object value = entity.get(property);
        if (!isValid(value)) {
          throw new ValidationException(property.getPropertyId(), value, value + " is invalid");


Provides the background color for entity property cells when displayed in a table.

define("entityID", [...]).setBackgroundColorProvider(new Entity.BackgroundColorProvider() {
      public Object getBackgroundColor(final Entity entity, final Property property) {
        if ("colorPropertyID") && entity.getString("colorPropertyID").equals("CYAN")) {
          return Color.CYAN;
        return null;

Example: EmpDept domain

Entities in action

Using the Entity class is rather straight forward.

//initialize the domain model by instantiating the domain model class
Store store = new Store();
//Initialize a database connection provider using the domain model, user credentials scott/tiger and application identifier TestApp
EntityConnectionProvider connectionProvider = EntityConnectionProviders.createConnectionProvider(
             store, new User("scott", "tiger"), "TestApp");
EntityConnection connection = connectionProvider.getConnection();
//Initialize a new entity representing the table STORE.ADDRESS
Entity address = store.entity(Store.T_ADDRESS);
//Set the value of the column ID to 42
address.put(Store.ADDRESS_ID, 42);
//Set the value of the column STREET to "Elm Street"
address.put(Store.ADDRESS_STREET, "Elm Street");
//Set the value of the column CITY to "Seattle"
address.put(Store.ADDRESS_CITY, "Seattle");
//Insert the address entity
//Initialize a new entity representing the table STORE.CUSTOMER
Entity customer = store.entity(Store.T_CUSTOMER);
//Set the value of the column ID to 42
customer.put(Store.CUSTOMER_ID, 42);
//Set the value of the column FIRST_NAME to John
customer.put(Store.CUSTOMER_FIRST_NAME, "John");
//Set the value of the column LAST_NAME to Doe
customer.put(Store.CUSTOMER_LAST_NAME, "Doe");
//Set the reference value ADDRESS
customer.put(Store.CUSTOMER_ADDRESS_FK, address);
//Set the value of the column IS_ACTIVE to true
customer.put(Store.CUSTOMER_IS_ACTIVE, true);
//Insert the entity representing the customer John Doe,
//receiving the primary key of the new record in a list as a return value
List<Entity.Key> key = connection.insert(Arrays.asList(customer));
//Retrieve the ID value
Integer id = customer.getInteger(Store.CUSTOMER_ID);
//Retrieve the first name value
String firstName = customer.getString(Store.CUSTOMER_FIRST_NAME);
//Select the entity from the database by primary key
Entity customerByKey = connection.selectSingle(key.get(0));
//Select entities representing the table STORE.CUSTOMER by first name
List<Entity> entitiesByFirstName = connection.selectMany(Store.T_CUSTOMER, Store.CUSTOMER_FIRST_NAME, "Björn");


Using the EntityGenerator tool you can quickly generate a domain class containing the property Id constants for a given database schema as well as basic entity definitions.

The required command line arguments are: schema_name domain_class_package_name username password Additionally you can add a comma seperated list of tables to include.

java -Djminor.db.type=h2 -Djminor.db.embedded=true -cp dist/jminor.jar:lib/h2-1.1.114.jar:lib/log4j-1.2.15.jar:lib/jcalendar-1.3.2.jar PETSTORE org.petstore.domain.Petstore scott tiger


Domain model test


To unit test the CRUD operations on the domain model extend EntityTestUnit.

The unit tests are run within a single transaction which is rolled back after the test finishes, so these tests are pretty much guaranteed to leave no junk data behind.


The following methods all have default implementations which are based on randomly created property values, based on the constraints set in the domain model, override if the default ones are not working.

  • initializeReferenceEntity should initialize return an instance of the given entity type to use for a foreign key reference required for inserting the entity being tested.
  • initializeTestEntity should return a entity to use as basis for the unit test, that is, the entity that should be inserted, selected, updated and finally deleted.
  • modifyEntity should simply leave the entity in a modified state so that it can be used for update test, since the db layer throws an exception if an unmodified entity is updated. If modifyEntity returns an unmodified entity, the update test is skipped.

To run the full CRUD test for a domain entity you need to call the testEntity(String entityID) method with the ID of the given entity as parameter. You can either create a single testDomain() method and call the testEntity method in turn for each entityID or create a single testEntityName for each domain entity, as we do in the example below.

public class TestStore extends EntityTestUnit {
  public TestStore() {
    super(new Store());
  @Test //a unit test method for the address entity
  public void address() throws Exception {
    //The testEntity method performs basic insert/select/update/delete tests for the given entity
  @Test //a unit test method for the customer entity
  public void customer() throws Exception {
  protected void initializeReferenceEntity(String entityID) throws Exception {
    //see if the currently running test requires an ADDRESS entity
    if (entityIDs.contains(Store.T_ADDRESS)) {
      Entity address = new Entity(Store.ADDRESS);
      address.put(Store.ADDRESS_ID, 21);
      address.put(Store.ADDRESS_STREET, "One Way");
      address.put(Store.ADDRESS_CITY, "Sin City");
      return address;
    return super.initializeReferenceEntity(entityID);
  protected Entity initializeTestEntity(String entityID) {
    if (entityID.equals(Store.T_ADDRESS)) {
      //Initialize a entity representing the table STORE.ADDRESS,
      //which can be used for the testing
      Entity address = new Entity(Store.T_ADDRESS);
      address.put(Store.ADDRESS_ID, 42);
      address.put(Store.ADDRESS_STREET, "Street");
      address.put(Store.ADDRESS_CITY, "City");
      return address;
    else if (entityID.equals(Store.T_CUSTOMER)) {
      //Initialize a entity representing the table STORE.CUSTOMER,
      //which can be used for the testing
      Entity customer = new Entity(Store.T_CUSTOMER);
      customer.put(Store.CUSTOMER_ID, 42);
      customer.put(Store.CUSTOMER_FIRST_NAME, "Robert");
      customer.put(Store.CUSTOMER_LAST_NAME, "Ford");
      //the getReferenceEntity() method returns the entity initialized in initializeReferenceEntities()
      customer.put(Store.CUSTOMER_ADDRESS_FK, getReferenceEntity(Store.T_ADDRESS));
      customer.put(Store.CUSTOMER_IS_ACTIVE, true);
      return customer;
    return null;
  protected void modifyEntity(Entity testEntity) {
    if ( {
      testEntity.put(Store.ADDRESS_STREET, "New Street");
      testEntity.put(Store.ADDRESS_CITY, "New City");
    else if ( {
      //It is sufficient to change the value of a single property, but the more the merrier
      testEntity.put(Store.CUSTOMER_FIRST_NAME, "Jesse");
      testEntity.put(Store.CUSTOMER_LAST_NAME, "James");
      testEntity.put(Store.CUSTOMER_IS_ACTIVE, false);




The EntityEditModel interface defines the CRUD business logic used by the EntityEditPanel class when entities are being edited, and must be defined for each entity requiring a CRUD user interface. The EntityEditModel works with a single entity instance, called the active entity, which can be set via the setEntity(Entity entity) method and retrieved via getEntityCopy(). The EntityEditModel interface exposes a number of methods for manipulating as well as querying the property values of the active entity, via the ValueMapEditModel interface which it extends

public class CustomerEditModel extends SwingEntityEditModel {
  public CustomerEditModel(EntityConnectionProvider connectionProvider) {
    super(Store.T_CUSTOMER, connectionProvider);
public class AddressEditModel extends SwingEntityEditModel {
  public AddressModel(EntityConnectionProvider connectionProvider) {
    super(Store.T_ADDRESS, connectionProvider);
    addDetailModel(new CustomerModel(connectionProvider));
//Initialize a database provider object using the credentials scott/tiger and application identifier TestApp
EntityConnectionProvider connectionProvider =
       EntityConnectionProviders.createConnectionProvider(new Store(), new User("scott", "tiger"), "TestApp");
CustomerEditModel customerModel = new CustomerEditModel(connectionProvider);
customerModel.setValue(Store.CUSTOMER_ID, 42);
customerModel.setValue(Store.CUSTOMER_FIRST_NAME, "Björn");
customerModel.setValue(Store.CUSTOMER_LAST_NAME, "Sigurðsson");
customerModel.setValue(Store.CUSTOMER_IS_ACTIVE, true);
//inserts the active entity
List<Entity.Key> primaryKeys = customerModel.insert();
//select the customer we just inserted
Entity customer = connectionProvider.getConnection().selectSingle(primaryKeys.get(0));
//set the customer as the entity to edit in the edit model
//modify some property values
customerModel.setValue(Store.CUSTOMER_FIRST_NAME, "John");
customerModel.setValue(Store.CUSTOMER_LAST_NAME, "Doe");
//updates the active entity
//deletes the active entity

Detail models

Directly adding a detail models is a trivial matter, the framework handles everything as long as the master/detail relationship is defined in the domain model.

addDetailModel(new CustomerModel(connectionProvider);

Table model

Each EntityModel can contain a single EntityTableModel instance. This table model can be created automatically by the EntityModel or supplied via a constructor argument in case of a specialized implementation.

static class TestTableModel extends SwingEntityTableModel {
  public TestTableModel(EntityConnectionProvider connectionProvider) {
    super("entityID", connectionProvider);
static class TestModel extends SwingEntityModel {
  public TestModel(EntityConnectionProvider connectionProvider) {
    super(new TestTableModel(connectionProvider));

Edit model

Each EntityModel contains a single EntityEditModel instance. This edit model can be created automatically by the EntityModel or supplied via a constructor argument in case of a specialized implementation.

static class TestEditModel extends SwingEntityEditModel {
  public TestEditModel(EntityConnectionProvider connectionProvider) {
    super("entityID", connectionProvider);
static class TestModel extends SwingEntityModel {
  public TestModel(EntityConnectionProvider connectionProvider) {
    super(new TestEditModel(connectionProvider));

Event binding

The EntityModel, EntityEditModel and EntityTableModel classes expose a number of addListener methods.

The following example prints, to the standard output, all changes made to a given property as well as a message indicating that a refresh has started.

protected void bindEvents() {
  getTableModel().addRefreshStartedListener(new EventListener() {
    public void eventOccurred() {
      System.out.println("Refresh is about to start");
  getEditModel().addValueListener(EmpDept.EMPLOYEE_DEPARTMENT_FK, new ValueChangeListener() {
    protected void valueChanged(ValueChangeEvent event) {
      System.out.println("Property " + e.getKey() + " changed from " + e.getOldValue() + " to " + e.getNewValue());




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 implementing this class you must provide a constructor taking a single EntityConnectionProvider instance as argument, as seen below.

public class StoreApplicationModel extends SwingEntityApplicationModel {
  public StoreApplicationModel(final EntityConnectionProvider connectionProvider) {
    addEntityModel(new CustomerModel(connectionProvider));

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 extending the abstract class org.jminor.framework.model.EntityLoadTestModel.

public class StoreLoadTest extends EntityLoadTestModel<StoreAppModel> {
  private static final Store STORE = new Store();
  public StoreLoadTest() {
    super(new User("scott", "tiger"));
  protected void performWork(final StoreAppModel application) {
    try {
      final EntityModel customerModel = application.getMainApplicationModels().iterator().next();
    catch (Exception e) {
  protected StoreAppModel initializeApplication() {
    return new StoreAppModel(new RemoteEntityConnectionProvider(STORE, getUser(), UUID.randomUUID(), getClass().getSimpleName()));
  public static void main(String[] args) {
    new LoadTestPanel(new StoreLoadTest()).showFrame();




The EntityPanel is the base UI class for working with entity instances. It usually consists of an EntityTablePanel, an EntityEditPanel, which contains the controls (text fields, combo boxes and such) for editing an entity instance and a set of detail panels representing the entities having a master/detail relationship with the underlying entity.


When instantiating an EntityPanel you can supply an EntityEditPanel instance as a parameter. When implementing an EntityEditPanel you must implement the initializeUI() method, in which you should initialize 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(final EntityEditModel editModel) {
  protected void initializeUI() {
    //the firstName field should receive the focus whenever the panel is initialized
    setLayout(new GridLayout(4,1));
    createCheckBox(Store.CUSTOMER_IS_ACTIVE, null, false);
    //the createControlPanel method creates a panel containing the
    //component associated with the property as well as a JLabel with the
    //property caption as defined in the domain model

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.

public TestPanel extends EntityPanel {
  public TestPanel(EntityModel model) {
    super("caption", model);
    addDetailPanel(new CustomerPanel(model.getDetailModel(CustomerModel.class)));

Custom actions

The action mechanism used throughout the JMinor framework is based on the Control class and its subclasses and the ControlSet class which, as the name suggests, represents a set of controls. There are two static utility classes for creating and presenting controls, Controls and ControlProvider respectively.


Provides methods for initializing MethodControl objects as well as ToggleBeanValueLink objects.

public class MethodControlTest {
  public void printString() {
    System.out.println("printing a string");
  public static void main(String[] args) {
    MethodControlTest testObj = new MethodControlTest();
    MethodControl control = Controls.methodControl(testObj, "printString", "Print string");
    //calls testObj.printString()
    control.actionPerformed(new ActionEvent(testObj, 0, "actionPerformed"));


The ControlProvider class provides static factory methods for creating UI components based on Control objects.

public class MethodControlTest {
  public void printString() {
    System.out.println("printing a string");
  public static void main(String[] args) {
    MethodControlTest testObj = new MethodControlTest();
    MethodControl control = Controlsy.methodControl(testObj, "printString", "Print string");
    JButton printButton = ControlProvider.createButton(control);
    //calls testObj.printString()

Adding a print action

The most common place to add a custom control is the table popup menu, f.ex. an action for printing reports. The table popup menu is based on the ControlSet returned by the getTablePopupControlSet() method in the EntityPanel class which in turn uses the ControlSet returned by the getPrintControls() method in the same class for constructing the print popup submenu. So, to add a custom print action you override the getPrintControls() method and return a ControlSet containing the action.

public ControlSet getPrintControls() {
  ControlSet printControls = new ControlSet("Print");
  //creates a MethodControl which calls the viewReport method in this class on activation
  printControls.add(Controls.methodControl(this, "viewReport", "Report"));
  //add the default print table control as well
  return printControls;



Reporting with JasperReports


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

With the JMinor JasperReports plugin you can either design your report based on a SQL query in which case you use the JasperReportsWrapper 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 JasperReportsEntityDataSource class, which is constructed around an iterator.

The EntityPanel class provides straight forward methods for viewing reports using methods provided by the EntityModel class for filling them. Both these classes rely on static utility classes for doing the actual work so you are not bound by the EntityPanel and EntityModel classes for viewing reports, they simply provide the easiest way of doing so.

JDBC Reports

Using a report based on a SQL query, JasperReportsWrapper and JasperReportsUIWrapper is the simplest way of viewing a report using JMinor, just add a method similar to the one below to a EntityPanel 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 void viewCustomerReport() throws Exception {
  List<Entity> selectedCustomers = getModel().getTableModel().getSelectedItems();
  if (selectedCustomers.getSize() == 0)
  String reportPath = System.getProperty(Configuration.REPORT_PATH)
          + "/customer_report.jasper";
  Collection<Object> customerIds =
          EntityUtil.getValues(selectedCustomers, Store.CUSTOMER_ID);
  HashMap<String, Object> reportParameters = new HashMap<String, Object>();
  reportParameters.put("CUSTOMER_IDS", customerIds);
  viewJdbcReport(new JasperReportsWrapper(reportPath, reportParameters), new JasperReportsUIWrapper(),  "Customer Report");

JRDataSource Reports

The JRDataSource implementation provided by the JasperReportsEntityDataSource simply iterates through the iterator received via the constructor and retrieves the field values from the underlying entities. For this to work you must design the report using field names that correspond to the property IDs, so using the Store domain example from above the fields in a report showing the available items would have to be named 'name', 'is_active', 'category_code' etc. If you need to use a field that does not correspond to a property in the underlying entity, f.ex. when combining two fields into one you must override the getFieldValue() method and handle that special case there.

public Object getFieldValue(JRField jrField) {
  if (jrField.getName().equals("name_category_code") {
    Entity currentRecord = getCurrentEntity();
    return currentRecord.getString(Store.ITEM_NAME) + " - "
             + currentRecord.getAsString(Store.ITEM_CATEGORY_CODE);
  return super.getFieldValue(jrField);

The way you view the report is just like in the jdbc report example above except you use the viewReport() method instead of viewJdbcReport().


documentation/manual.txt · Last modified: 2019/04/24 14:27 by darri