A newer version is available at the documentation home.

Preface

This manual provides the reference information for the CUBA platform and covers the most important topics of developing business applications with it.

Intended Audience

This manual is intended for application developers using the CUBA platform. The following technologies knowledge is required to use the platform:

  • Java Standard Edition

  • Relational databases (SQL, DDL)

Further Reading

This manual and other documentation related to the CUBA platform can be found at www.cuba-platform.com/manual.

Video materials and presentations that can help you to understand the platform are available at www.cuba-platform.com/tutorials.

You can also check out online demo applications at www.cuba-platform.com/online-demo.

Knowledge of the following technologies and frameworks will be helpful to get a deeper understanding of the platform:

Feedback

If you have any suggestions for improvement of this Manual, please contact support at www.cuba-platform.com/support/topics.

If you find a mistake in the documentation, please specify the number of the chapter and attach a small portion of the surrounding text to facilitate the search.

1. Introduction

This chapter provides information about the CUBA platform features and requirements.

1.1. Overview

CUBA platform is an ideal tool for development teams working on line-of-business applications, typically having extensive data model, hundreds of screens and complex business logic.

Based on a mainstream technology stack, CUBA platform brings unparalleled productivity by utilizing a rich set of ready to use data-aware components, extensive scaffolding, visual interface designer and hot deploy.

Open architecture allows a developer to customize any part of the framework, providing high levels of control and flexibility. Developers have the freedom to use popular Java IDEs and have full access to the source code.

CUBA applications fit seamlessly into the corporate IT environment, supporting major databases and application servers, as well as popular aPaaS clouds. Streamlined clustered deployment ensures scalability and failover, while a generic REST API enables easy integration with other systems.

1.2. Technical Requirements

Minimum requirements for development using CUBA platform:

  • Memory – 4 GB

  • Hard drive space – 5 GB

  • Operating system: Microsoft Windows, Linux or Mac OS X

1.3. Release Notes

2. Installation and Setup

Minimum software requirements are as follows:

  • Java SE Development Kit (JDK) 8. It is recommended that you use Oracle Java HotSpot VM.

    In order to build and run projects outside Studio, you need to set the path to the JDK root directory in the JAVA_HOME environment variable, e.g. C:\Program Files\Java\jdk1.8.0_60. On Windows, you can do this at ComputerPropertiesAdvanced System SettingsAdvancedEnvironment variables. The value of the variable should be added to the System variables list.

  • Java IDE: IntelliJ IDEA Community Edition 13+ or Eclipse 4.3+. We recommend using IntelliJ IDEA.

In the most basic scenario, the built-in HyperSQL (http://hsqldb.org) can be used as the database server. This is sufficient for exploring the platform capabilities and application prototyping. For building production applications, it is recommended to install and use one of the full-featured DBMS supported by the platform, like PostgreSQL for instance.

The web interface of the platform-based applications supports all popular browsers, including Google Chrome, Mozilla Firefox, Safari, Opera 15+, Internet Explorer 9+, Microsoft Edge.

2.1. CUBA Studio Installation

Prerequisites:

  • Make sure that Java SE Development Kit (JDK) 8 is installed by running the following command in the console:

    java -version

    The command should return the Java version, e.g. 1.8.0_60.

  • If you connect to the internet via a proxy server, some Java system properties must be passed to the JVM running Studio and Gradle. These properties are explained here: http://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html (see properties for HTTP and HTTPS protocols).

    It is recommended to set these properties system-wide in the JAVA_OPTS environment variable. The Studio launch script passes JAVA_OPTS to the Java executable.

In order to install CUBA Studio, take the following steps:

  1. Download an appropriate installer or ZIP archive from https://www.cuba-platform.com/download.

  2. Run the installer or unzip the archive to a local directory, e.g. c:/work/studio

  3. Launch the installed application or open the command line, go to bin directory and run studio

  4. In the CUBA Studio Server window, enter the following parameters:

    • Java home − JDK installation to be used for building and running projects. If you have set the JAVA_HOME environment variable as described in the beginning of this chapter, it will appear in this field. Otherwise, Studio will try to find your Java installation itself.

    • Advanced button opens a dialog that allows you to enter a path to Gradle home directory. Leave it empty; in this case, the required Gradle distribution will be downloaded automatically.

      If you want to use a local Gradle distribution, enter the path to the respective directory. For project build system to work correctly, Gradle 2.13 is required.

    • Server port − CUBA Studio server port (the default port is 8111).

    • IDE port − IDE plugin listening port (the default port is 48561).

    • Repository − binary artifacts repository URL and authentication parameters.

      studio server window

    The following options are also available:

    • Check for updates - check for new versions on every start.

    • Help language - built-in help language.

    • Offline - enable working with projects without an Internet connection, provided that all the required libraries have been previously downloaded from the repository.

    • Send anonymous statistics and crash reports - enable Studio to send error statistics to developers.

    • Enable remote connection - by default, it is assumed that Studio runs on localhost. Select this checkbox if you need to connect to this Studio instance from a remote host.

    • Silent startup - if selected, the Studio server starts in tray and opens UI in default browser automatically. This option is available only for Windows and Mac OS X.

  5. Click Start to run the Studio server.

    The server will download, run, and connect to the Gradle daemon. This may take a significant amount of time on first startup; on subsequent launches, this will take just a few seconds.

    After that, the web server will be started, and the URL of the Studio interface will appear in the URL field. By clicking , you can open the address in your default web browser; by clicking Copy you can copy the address to the clipboard.

  6. Open the specified address in the web browser.

  7. In the Studio web interface, click Create new to create a new project, or Import to add an existing one to the Recent list.

  8. Once the project is opened, the Studio will download the source code of the platform base projects and save it to the local folder. Before building the project, it is recommended to wait until the download is finished and make sure that the background task indicator in the bottom left corner has faded out.

2.2. IDE Integration

Take the following steps to integrate Studio with IntelliJ IDEA or Eclipse:

  1. Open or create a new project in the Studio.

  2. Switch to Project properties section and click Edit. Select the required Java IDE by checking IntelliJ IDEA or Eclipse.

  3. Select Build > Create or update <IDE> project files in the Studio menu. The corresponding files will be created in the project directory.

  4. For IntelliJ IDEA integration:

    1. Run IntelliJ IDEA 13+ and install CUBA Framework Integration plugin, from the plugin repository: File > Settings > Plugins > Browse Repositories.

  5. For Eclipse integration:

    1. Run Eclipse 4.3+, open Help > Install New Software, add http://files.cuba-platform.com/eclipse-update-site repository and install the CUBA Plugin.

    2. In the CUBA section of the Window > Preferences menu, check Studio Integration Enabled, and click OK.

Please note that IDE: on port 48561 label has appeared in the bottom left corner of the Studio. Now the corresponding source code files will be opened in IDE when you click IDE buttons in the Studio.

3. Quick Start

This section describes the process of creating an application using CUBA Studio. Similar information is provided in the videos available at www.cuba-platform.com/quickstart.

Make sure that the necessary software is already installed and set up on your computer, see Installation and Setup.

Key stages of our application development:

  1. Data model development including creation of entities describing application domain and corresponding database tables.

  2. Development of the user interface screens enabling to create, view, update and delete data model entities.

3.1. Application Details

The application should maintain information about the customers and their orders.

A customer has the following attributes:

  • Name

  • E-mail

Order attributes:

  • Ownership by a customer

  • Date

  • Amount

The application UI should contain:

  • Customers browser screen;

  • Customer editor screen, containing as well the list of this customer’s orders;

  • General orders browser screen;

  • Order editor screen.

The application should support user interface in English and Russian.

3.2. Creating a Project

  1. Start CUBA Studio and open its web interface (See CUBA Studio Installation).

  2. Click Create new.

  3. Specify the name of the new project in the Project name field of the New project window – for example, sales. The name should contain only Latin letters, numbers and underscores. Think carefully on the project name at this stage, as changing it later on will require complex manual intervention.

  4. The following fields below will be automatically populated:

    • Project path – the path to the new project directory. You can select the directory manually by clicking the …​ button next to the field. The Select folder window will appear with the list of folders n your hard drive. You can select one of those, or create a new directory by clicking the + button.

    • Project namespace – the namespace which will be used as a prefix for entity names and database tables. The namespace can consist of Latin letters only and should be as short as possible. For example, if the project name is sales_2, the namespace can be sales or sal.

    • Root package − the root package of Java classes. It can be adjusted later, but the classes generated at project creation will not be moved.

    • Base projects version – the platform version used in the project. The platform artifacts will be automatically downloaded from the repository on project build.

  5. Click OK. Empty project will be created in the specified sales directory and the main Studio window will open.

  6. Assemble the project: select option Build > Assemble project in the Studio main menu. At this stage all required libraries will be downloaded and project artifacts will be assembled in build subdirectories of the modules.

  7. Create the database on the local HyperSQL server: select option Run > Create database in the menu. The database name is the same as project namespace by default.

  8. Select Run > Deploy menu option. Tomcat server with the deployed application will be installed in the project build subdirectory.

  9. Select Run > Start application server option. The link next to the Web application caption in the status panel will become available in a few seconds so you will be able to open the application directly from Studio.

    The username and password are admin / admin.

    The running application contains two main menu items (Administration and Help), as well as security and administration subsystems functionality.

3.3. Creating Entities

Let us create the Customer entity class.

  • Go to the Entities tab in the navigation section and click New entity. The New entity dialog window will appear.

  • Enter the name of the entity class – Customer – in the Class name field.

    qs create customer entity
  • Click OK. The entity designer page will be displayed in the workspace.

    qs customer entity
  • The entity name and the database table name will be automatically generated in the Name and the Table fields respectively.

  • Leave the existing value – StandardEntity - in the Parent class field.

  • Leave the Inheritance strategy field blank.

  • Click localization button next to the Name to open the Localized message window. Specify localization for the entity name for the available languages in it.

Next, let us create entity attributes. To do this, click the New button below the Attributes table.

  • Create attribute window will appear. Enter the name of the entity attribute − name, in the Name field. Select DATATYPE value in the Attribute type list, specify String attribute type in the Type field and then set the length of the text attribute to 100 characters in the Length field. Check the Mandatory box. The name of the database table column will be automatically generated in the Column field.

    qs new attribute

    Now click localization button next to the attribute name to open the Localized message window. Localize the attribute name in the available languages.

    Click Add to add the attribute.

  • email attribute is created in the same way but the value in Length field should be set to 50.

After creating the attributes, go to the Instance name tab in the entity designer to specify Name pattern. Select the name attribute in the Available attributes list and move it to the Name pattern attributes list by clicking the button with the right arrow on it.

qs customer instance name

Customer entity creation is now complete. Click OK in the top panel to save the changes and close the page.

Let us create the Order entity. Click New entity option on the Entities tab. Enter the Class nameOrder. The entity should have the following attributes:

  • Namecustomer, Attribute typeASSOCIATION, TypeCustomer, CardinalityMANY_TO_ONE.

  • Namedate, Attribute typeDATATYPE, TypeDate. Check Mandatory box for date attribute.

  • Nameamount, Attribute typeDATATYPE, TypeBigDecimal.

Specify localized caption for each of the attributes by clicking the localization button next to the attribute name.

3.4. Creating Database Tables

It is sufficient to click Generate DB scripts button in Entities tab on the navigation panel to create database tables. After that, Database scripts page will open. Both incremental DB update scripts from the current state (Update scripts) and initial DB creation scripts (Init tables, Init constraints, Init data) will be generated on this page.

qs generate db scripts

Click Save and close button to save the generated scripts. To run update scripts, stop the running application using the Run > Stop application server command, then select Run > Update database.

3.5. Creating User Interface Screens

Now we will create screens for customers and orders data management.

3.5.1. Screens for Customer

Select Customer entity in the Entities tab on the navigation panel to create standard screens for viewing and editing Customers. Click Create standard screens link at the bottom of the section. After that, Create standard screens page will appear.

qs create customer screens

All fields in this dialog are already populated with default values, there is no need to change them. Click the Create button.

customer-edit.xml and customer-browse.xml items will appear in GUI Module on Screens tab of the navigation panel.

You can specify localized captions for the screens. For this, select a screen and click Edit to open the screen designer page. Go to the Properties tab. Click the localization[] button next to the Caption field and specify screen names in different locales. Alternatively, you can open messages.properties item located in the screens package and edit browseCaption and editCaption messages for available locales.

3.5.2. Order Screens

Order entity has the following distinction: since one of the attributes is the Order.customer reference attribute, you should define a view including this attribute (standard _local view does not include reference attributes).

Go to the Entities tab on the navigation panel, select the Order entity and click the New view button. View designer page will open. Enter orderWithCustomer as the view name, click on customer attribute and select _minimal view for the Customer entity in the panel on the right.

qs order view

Click OK in the top panel.

After that, select the Order entity and click Create standard screens. Select orderWithCustomer in the View fields in both browser and editor panels of the Create standard screens page and click Create.

qs create order screens

order-edit.xml and order-browse.xml items will appear in the GUI Module on the Screens tab of the navigation panel.

You can specify localized captions for the Order screens as described above for the Customer screens.

3.5.3. Application Menu

At the moment of their creation, the screens were added to the application menu item of the default application menu. Let us rename it. Switch to the Main menu tab on the navigation panel and click Edit. The Menu designer page will open. Select the application menu item to edit its properties.

Enter the new value of the menu identifier − shop − in the Id field, then click the Caption edit button and set localized names of the menu item.

qs application menu

After editing the menu, click OK in the top panel.

3.5.4. Customer Editor With a List of Orders

Do the following to display the list of Orders in the Customers edit screen:

  • Go to the Screens tab on the navigation panel. Choose customer-edit.xml screen and click Edit.

  • Go to the Datasources tab on the screen designer page and click New.

  • Select the newly created data source in the list. Its attributes will appear in the right part of the page.

  • Specify collectionDatasource in the Type field.

  • In Id field enter the data source identifier − ordersDs.

  • Select com.sample.sales.entity.Order entity in the Entity list.

  • Select _local view in the View list.

  • Enter the following query in the Query field:

    select o from sales$Order o where o.customer.id = :ds$customerDs order by o.date

    The query contains orders selection criterion with ds$customerDs parameter. The parameter value named like ds${datasource_name} will contain id of the entity selected in datasource_name datasource at the moment, in this case it is the id of the Customer being edited.

    qs customer screen orders ds
  • Click Apply to save the changes.

  • Next go to the Layout tab in the screen designer and find the Label component in the components palette. Drag this component to the screen components hierarchy panel and place it between fieldGroup and windowActions. Go to the Properties tab in the properties panel. Enter msg://orders in the value field. Click the localization button next to the value field and define label values in available languages.

    qs customer screen label
Tip

If the application is not intended to be used in multiple languages, the value in the value field can be entered straight in the required language.

  • Drag Table from the components palette to components hierarchy panel and place it between label and windowActions. Select this component in the hierarchy and specify table size in properties on the Layout tab: set 100% in the width field and 200px in the height field.

    Go to the Properties tab. Set ordersTable value as id, choose orderDs from the list of available datasources.

    qs customer screen table
  • Click OK in the top panel to save the changes in the screen.

3.6. Running the Application

Now let us see how the created screens look in the actual application. Select Run > Restart application server.

Log in selecting English language in the login window. Open the Sales > Customers menu item:

qs customer browse
Figure 1. The Customers browser

Click Create:

qs customer edit 2
Figure 2. The Customer editor screen

Open the Sales > Orders menu item:

qs order browse
Figure 3. The Orders browser

Click Create:

qs order edit
Figure 4. The Order editor

4. The Framework

This chapter contains detailed description of the platform architecture, components and mechanisms.

4.1. Architecture

This section covers the architecture of CUBA applications in different aspects: in regard to tiers, blocks, modules, and to the used basic projects.

4.1.1. Application Tiers and Blocks

The platform enables building applications according to the classic three-tier pattern: client tier, middleware tier, database. The tier indicates the degree of "remoteness" from the stored data.

Further on, mainly middleware and client tiers will be described, therefore the words "all tiers" will refer to these tiers only.

Each tier enables creating one or more application blocks. A block is a separate executable program interacting with other blocks in the application. CUBA platform tools enable creation of blocks in the form of web or desktop applications. Development for mobile platforms currently remains beyond CUBA framework; however, mobile blocks made up using other tools can be integrated with the standard blocks of the application.

AppTiers
Figure 5. Application Tiers and Blocks
Middleware

The middle tier contains core business logic of the application and provides access to the database. It is represented by a separate web application running on Java EE Web Profile standard container. See Middleware Components.

Web Client

The main block of the client tier. It contains the interface designed primarily for internal users. It is represented by a separate web application running on Java EE Web Profile standard container. The user interface is implemented on the base of Vaadin framework. See Generic User Interface.

Desktop Client

The additional block of the client tier. It contains the interface designed primarily for internal users. It is represented by a desktop Java application; the user interface is implemented on the base of Java Swing framework. See Generic User Interface.

Web Portal

The additional block of the client tier. It contains the interface for external users and integration tools for mobile devices and third-party applications. It is represented by a separate web application running under Java EE Web Profile standard container. The user interface is implemented on the base of Spring MVC framework. See Portal Components.

Middleware is the mandatory block for any application. User interface is generally implemented by one or several blocks, such as Web Client and Web Portal.

The above mentioned blocks are standard, however, in order to separate the functionality in a complex application one can easily create any number of client blocks as well as middleware blocks.

All of the client blocks interact with the middle tier uniformly via HTTP protocol enabling to place the middle tier arbitrarily, behind firewall as well. It is worth mentioning, that in the simplest case when the middle tier and the web client are deployed on the same server local interaction between them can bypass the network stack in order to reduce overhead.

4.1.2. Application Modules

A module is the smallest structural part of CUBA application. It is a single module of application project and the corresponding JAR file with executable code.

Standard modules:

  • global – includes entity classes, service interfaces, and other classes common for all tiers. It is used in all application blocks.

  • core – implements services and all other components of the middle tier. It is used only in Middleware.

  • gui – common components of the generic user interface. It is used in Web Client and Desktop Client.

  • web – the implementation of the generic user interface based on Vaadin and other specific web client classes. It is used in Web Client block.

  • desktop – an optional module – implementation of generic user interface based on Java Swing, as well as other specific desktop client classes. It is used in Desktop Client block.

  • portal – an optional module – implementation of Web portal based on Spring MVC.

AppModules
Figure 6. Application Modules

4.1.3. Base Projects

The functionality of the platform is divided into several so-called base projects:

  • cuba – the main base project containing all of the functionality described in this manual

  • reports – reports generating subsystem

  • fts – full-text search subsystem

  • charts – subsystem for displaying charts and maps

  • ccpayments – subsystem dealing with credit cards

  • bpm – the mechanism of business processes execution according to the BPMN 2.0 standard

The application created on the platform can comprise the functionality of the base projects by declaring dependencies on their artifacts. Dependence on cuba artifacts is mandatory. Optional base projects in turn also depend on cuba, and may contain dependencies between them.

BaseProjects
Figure 7. Dependencies between Projects

Solid lines demonstrate mandatory dependencies, dashed lines mean optional ones.

4.1.4. Application Structure

The above-listed architectural principles are directly reflected in the composition of assembled application. Let us consider the example of a simple application sales, which has two blocks – Middleware and Web Client; and includes functionality of the two base projects cuba and reports.

SampleAppArtifacts
Figure 8. The structure of a simple application

The figure demonstrates the contents of several directories of the Tomcat server with a deployed application sales in it.

The Middleware block is represented by the app-core web application, the Web Client block – by the app web application. The executable code of the web applications can be found in directories WEB-INF/lib in sets of JAR files. Each JAR (artifact) is a result of assembly of one of the application or base project modules.

For instance, the contents of JAR files of the web application in middle tier app-core is determined by the facts that the Middleware block includes global and core modules, and the application uses cuba and reports base projects.

4.2. Common Components

This chapter covers platform components, which are common for all tiers of the application.

4.2.1. Data Model

Data model entities are divided into two categories:

  • Persistent – instances of such entities are stored in the database tables.

  • Non-persistent – instances exist only in memory.

The entities are characterized by their attributes. An attribute corresponds to a field and a pair of access methods (get / set) of the field. If the setter is omitted, the attribute becomes read only.

Persistent entities may include attributes that are not stored in the database. For non-persistent attribute the field is optional, creation of access methods will be sufficent.

The entity class should meet the following requirements:

  • Be inherited from one of the base classes provided by the platform (see below).

  • Have a set of fields and access methods corresponding to the entity attributes.

  • The class and its fields (or access methods if the attribute has no corresponding field) must be annotated in a definite way for correct operation of the ORM (in case of a persistent entity) and metadata frameworks.

  • To support potential entity extension, fields should be declared with the protected modifier instead of private.

The following attribute types of entities are supported:

  • java.lang.String

  • java.lang.Boolean

  • java.lang.Integer

  • java.lang.Long

  • java.lang.Double

  • java.math.BigDecimal

  • java.util.Date

  • java.sql.Date

  • java.sql.Time

  • java.util.UUID

  • byte[]

  • enum

  • entity

Base entity classes (see below) override equals() and hashCode() methods to check entity instances equivalence by comparing their identifiers. I.e., instances are considered equal, if their identifiers are equal. An identifier of the UUID type is assigned to an instance right after its creation in memory, which is why new instances can also be compared and added to collections.

4.2.1.1. Base Entity Classes

The base entity classes and interfaces are described in detail in this section.

EntityClasses
Figure 9. Base Entity Classes
  • Instance – declares the basic methods for working with objects of application domain:

    • getting the global unique identifier (UUID) of the entity

    • getting references to the object meta-class

    • generating the instance name

    • reading/writing attribute values by name

    • adding listeners receiving notifications about attribute changes

  • Entity – extends Instance with entity identifier (which is not necessarily equal to the UUID); at the same time Entity does not define the type of the identifier leaving this option to descendants.

  • AbstractInstance – implements the logic of working with attribute change listeners.

    Warning

    AbstractInstance stores the listeners in WeakReference, and if there are no external references to the added listener, it will be immediately destroyed by garbage collector. Normally, attribute change listeners are visual components and UI datasources that are always referenced by other objects, so there is no problem with listeners dropout. However, if a listener is created by application code and no objects refer to it in a natural way, it is necessary to save it in a certain object field apart from just adding it to Instance.

  • AbstractNotPersistentEntity – base class of non-persistent entities with UUID identifier.

  • BaseEntity – base class of persistent entities; declares methods for working with information about when and by whom the entity instance was created in the database.

  • BaseGenericIdEntity – implements BaseEntity with added JPA annotations without specifying the type of the identifier (i.e. the primary key) of the entity.

  • BaseUuidEntity – extends BaseGenericIdEntity and sets the id identification attribute of the UUID type.

  • BaseLongIdEntity – extends BaseGenericIdEntity and sets the id identification attribute of the Long type.

  • BaseIntegerIdEntity – extends BaseGenericIdEntity and sets the id identification attribute of the Integer type.

  • BaseStringIdEntity – extends BaseGenericIdEntity and sets only the type of the identifier - String. A specific entity class, extended from BaseStringIdEntity, must have a String-type identifier attribute with the @Id JPA annotation.

  • Versioned – interface for entities supporting optimistic locking.

  • Updatable – interface for entities which require to keep the information about when and by whom the instance was last changed.

  • SoftDelete – interface for entities supporting soft deletion.

  • StandardEntity – the most commonly used base class of persistent entities that implements the interfaces given above.

When creating entity classes it is recommended to choose a base class according to the following rules:

  • If an entity is not stored in the database, inherit it from AbstractNotPersistentEntity.

  • If an entity is embedded, inherit it from EmbeddableEntity.

  • If an entity is only created in DB, never changes and does not need soft deletion, inherit it from BaseUuidEntity.

  • If an entity behaves in a standard way: changes in the database, requires optimistic locking and soft deletion − inherit it from StandardEntity.

  • Otherwise inherit the entity from BaseUuidEntity and implement Versioned, Updatable, SoftDelete interfaces if required.

  • For some entities, it is desirable to use integer or string primary keys. In this case, inherit the entity from BaseLongIdEntity, BaseIntegerIdEntity or BaseStringIdEntity instead of BaseUuidEntity.

4.2.1.2. Entity Annotations

This section describes all annotations of entity classes and attributes supported by the platform.

Annotations from the javax.persistence package are required for JPA, annotations from com.haulmont.* packages are designed for metadata management and other mechanisms of the platform.

In this manual, if an annotation is identified by a simple class name, it refers to a platform class, located in one of the com.haulmont.* packages.

4.2.1.2.1. Class Annotations
@javax.persistence.Entity

Declares a class to be a data model entity.

Parameters:

  • name – the name of the entity, must begin with a prefix, separated by a $ sign. It is recommended to use a short name of the project as a prefix to form a separate namespace.

Example:

@Entity(name = "sales$Customer")
@javax.persistence.MappedSuperclass

Defines that the class is an ancestor for some entities and its attributes must be used as part of descendant entities. Such class is not associated with any particular database table.

@javax.persistence.Table

Defines database table for the given entity.

Parameters:

  • name – the table name

Example:

@Table(name = "SALES_CUSTOMER")
@javax.persistence.Embeddable

Defines an embedded entity stored in the same table as the owning entity.

@MetaClass annotation should be used to specify the entity name.

@javax.persistence.Inheritance

Defines the inheritance strategy to be used for an entity class hierarchy. It is specified on the entity class that is the root of the entity class hierarchy.

Parameters:

  • strategy – inheritance strategy, SINGLE_TABLE by default

javax.persistence.DiscriminatorColumn

Is used for defining a database column responsible for the distinction of entity types in the cases of SINGLE_TABLE and JOINED inheritance strategies.

Parameters:

  • name – the discriminator column name

  • discriminatorType – the discriminator column type

Example:

@DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.INTEGER)
@javax.persistence.DiscriminatorValue

Defines the discriminator column value for this entity.

Example:

@DiscriminatorValue("0")
@javax.persistence.PrimaryKeyJoinColumn

Is used in the case of JOINED inheritance strategy to specify a foreign key column for the entity which refers to the primary key of the ancestor entity.

Parameters:

  • name – the name of the foreign key column of the entity

  • referencedColumnName – the name of primary key column of the ancestor entity

Example:

@PrimaryKeyJoinColumn(name = "CARD_ID", referencedColumnName = "ID")
@NamePattern

Determines the way of getting the name of the instance returned by the Instance.getInstanceName() method.

The annotation value should be a string in the {0}|{1} format, where:

  • {0} – formatting string according to the String.format() rules, or this object method name with the prefix #. The method should return String and should have no parameters.

  • {1} – a list of field names separated by commas, corresponding to {0} format. If the method is used in {0}, the list of fields is still required as it forms the _minimal view.

Examples:

@NamePattern("%s|name")
@NamePattern("#getCaption|login,name")
@Listeners

Defines the list of listeners intended for reaction to the entity instance lifecycle events on the middle tier.

The annotation value should be a string or an array of strings containing class names of the listeners. See Entity Listeners.

The strings here are used instead of class references because classes of the listeners are contained only on middleware and are inaccessible for client code, while the classes of the entities are used on all tiers.

Examples:

@Listeners("com.haulmont.cuba.security.listener.UserEntityListener")
@Listeners({"com.company.sales.entity.FooListener","com.company.sales.entity.BarListener"})
@MetaClass

Is used for declaring non-persistent or embedded entity (meaning that @javax.persistence.Entity annotation cannot be applied)

Parameters:

  • name – the entity name, must begin with a prefix, separated by a $ sign. It is recommended to use a short name of the project as prefix to form a separate namespace.

Example:

@MetaClass(name = "sales$Customer")
@SystemLevel

Indicates that the entity is system only and should not be available for selection in various lists of entities, such as generic filter parameter types or dynamic attribute type.

@EnableRestore

Indicates that the entity instances are available for recovery after soft deletion on the core$Entity.restore screen available through the Administration > Data Recovery main menu item.

@TrackEditScreenHistory

Indicates that editor screens opening history will be recorded with the ability to display it on the sec$ScreenHistory.browse screen available through the Help > History main menu item.

@Extends

Indicates that the entity is an extension and it should be used everywhere instead of the base entity. See Functionality Extension.

@PostConstruct

This annotation can be specified for a method. Such method will be invoked right after the entity instance is created by the Metadata.create() method. This is convenient when instance initialization requires invocation of managed beans. For example, see Entity Fields Initialization.

4.2.1.2.2. Attribute Annotations

Attribute annotations should be set for the corresponding fields, with the following exception: if there is a need to declare read-only, non-persistent attribute foo, it is sufficient to create getFoo() method and annotate it with @MetaProperty.

@javax.persistence.Transient

Indicates that field is not stored in the database, meaning it is non-persistent.

The fields supported by JPA types (See http://docs.oracle.com/javaee/7/api/javax/persistence/Basic.html) are persistent by default, that is why @Transient annotation is mandatory for non-persistent attribute of such type.

@MetaProperty annotation is required if @Transient attribute should be included in metadata.

@javax.persistence.Column

Defines DB column for storing attribute values.

Parameters:

  • name – the column name.

  • length – (optional parameter, 255 by default) – the length of the column. It is also used for metadata generation and ultimately, can limit the maximum length of the input text in visual components bound to this attribute. Add the @Lob annotation to remove restriction on the attribute length.

  • nullable – (optional parameter, true by default) – determines if an attribute can contain null value. When nullable = false JPA ensures that the field has a value when saved. In addition, visual components working with the attribute can request the user to enter a value.

@javax.persistence.Id

Indicates that the attribute is the entity primary key. Typically, this annotation is set on the field of a base class, such as BaseUuidEntity. Using this annotation for a specific entity class is required only in case of inheritance from the BaseStringIdEntity base class (i.e. creating an entity with a string primary key).

@javax.persistence.ManyToOne

Defines a reference attribute with many-to-one relationship type.

Parameters:

  • fetch – (EAGER by default) parameter that determines whether JPA will eagerly fetch the referenced entity. This parameter should always be set to LAZY, since retrieval of referenced entity in CUBA-application is determined dynamically by the views mechanism.

  • optional – (optional parameter, true by default) – indicates whether the attribute can contain null value. If optional = false JPA ensures the existence of reference when the entity is saved. In addition, the visual components working with this attribute can request the user to enter a value.

For example, several Order instances refer to the same Customer instance. In this case the Order.customer attribute should have the following annotations:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CUSTOMER_ID")
protected Customer customer;
@javax.persistence.OneToMany

Defines a collection attribute with one-to-many relationship type.

Parameters:

  • mappedBy – the field of the referenced entity, which owns the relationship.

  • targetEntity – the type of referenced entity. This parameter is optional if the collection is declared using Java generics.

  • fetch – (optional parameter, LAZY by default) – determines whether JPA will eagerly fetch the collection of referenced entities. This parameter should always remain LAZY, since retrieval of referenced entities in CUBA-application is determined dynamically by the views mechanism.

For example, several Item instances refer to the same Order instance using @ManyToOne field Item.order. In this case the Order class can contain a collection of Item instances:

@OneToMany(mappedBy = "order")
protected Set<Item> items;
@javax.persistence.OneToOne

Defines a reference attribute with one-to-one relationship type.

Parameters:

  • fetch – (EAGER by default) determines whether JPA will eagerly fetch the referenced entity. This parameter should be set to LAZY, since retrieval of referenced entities in CUBA-application is determined dynamically by the views mechanism.

  • mappedBy – the field of the referenced entity, which owns the relationship. It must only be set on the non-owning side of the relationship.

  • optional – (optional parameter, true by default) – indicates whether the attribute can contain null value. If optional = false JPA ensures the existence of reference when the entity is saved. In addition, the visual components working with this attribute can request the user to enter a value.

Example of owning side of the relationship in the Driver class:

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CALLSIGN_ID")
protected DriverCallsign callsign;

Example of non-owning side of the relationship in the DriverCallsign class:

@OneToOne(fetch = FetchType.LAZY, mappedBy = "callsign")
protected Driver driver;
@javax.persistence.ManyToMany

Defines a collection attribute with many-to-many relationship type.

Many-to-many relationship always has an owning side and can also have inverse, non-owning side. The owning side should be marked with additional @JoinTable annotation, and the non-owning side – with mappedBy parameter.

Parameters:

  • mappedBy – the field of the referenced entity, which owns the relationship. It must only be set on the non-owning side of the relationship.

  • targetEntity – the type of referenced entity. This parameter is optional if the collection is declared using Java generics.

  • fetch – (optional parameter, LAZY by default) – determines whether JPA will eagerly fetch the collection of referenced entities. This parameter should always remain LAZY, since retrieval of referenced entities in CUBA-application is determined dynamically by the views mechanism.

@javax.persistence.JoinColumn

Defines DB column that determines the relationship between entities. Presence of this annotation indicates the owning side of the association.

Parameters:

  • name – the column name

Example:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CUSTOMER_ID")
protected Customer customer;
@javax.persistence.JoinTable

Defines a join table on the owning side of @ManyToMany relationship.

Parameters:

  • name – the join table name

  • joinColumns@JoinColumn element in the join table corresponding to primary key of the owning side of the relationship (the one containing @JoinTable annotation)

  • inverseJoinColumns@JoinColumn element in the join table corresponding to primary key of the non-owning side of the relationship.

Example of the customers attribute of the Group class on the owning side of the relationship:

@ManyToMany
@JoinTable(name = "SALES_CUSTOMER_GROUP_LINK",
 joinColumns = @JoinColumn(name = "GROUP_ID"),
 inverseJoinColumns = @JoinColumn(name = "CUSTOMER_ID"))
protected Set<Customer> customers;

Example of the groups attribute of the Customer class on non-owning side of the same relationship:

@ManyToMany(mappedBy = "customers")
protected Set<Group> groups;
@javax.persistence.OrderBy

Determines the order of elements in a collection attribute at the point when the association is retrieved from the database. This annotation should be specified for ordered Java collections such as List or LinkedHashSet to get a predictable sequence of elements.

Parameters:

  • value – string, determines the order in the format:

orderby_list::= orderby_item [,orderby_item]*
orderby_item::= property_or_field_name [ASC | DESC]

Example:

@OneToMany(mappedBy = "user")
@OrderBy("createTs")
protected List<UserRole> userRoles;
@javax.persistence.Embedded

Defines a reference attribute of embeddable type. The referenced entity should have @Embeddable annotation.

Example:

@Embedded
protected Address address;
@javax.persistence.Temporal

Specifies the type of the stored value for java.util.Date attribute: date, time or date+time.

Parameters:

  • value – the type of the stored value: DATE, TIME, TIMESTAMP

Example:

@Column(name = "START_DATE")
@Temporal(TemporalType.DATE)
protected Date startDate;
@javax.persistence.Version

Indicates that the annotated field stores a version for optimistic locking support.

Such field is required when an entity class implements the Versioned interface (StandardEntity base class already contains such field).

Example:

@Version
@Column(name = "VERSION")
private Integer version;
@javax.persistence.Lob

Indicates that the attribute does not have any length restrictions. This annotation is used together with the @Column annotation. If @Lob is set, the default or explicitly defined length in @Column is ignored.

Example:

@Column(name = "DESCRIPTION")
@Lob
private String description;
@MetaProperty

Indicates that metadata should include the annotated attribute. This annotation can be set for a field or for a getter method, if there is no corresponding field.

This annotation is not required for the fields already containing the following annotations from javax.persistence package: @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany, @Embedded. Such fields are included in metadata automatically. Thus, @MetaProperty is mainly used for defining non-persistent attributes of the entities.

Parameters (optional):

  • mandatory - determines whether the attribute can contain null value. If mandatory = true, visual components working with this attribute can request the user to enter a value.

  • datatype - explicitly defines a datatype that overrides a datatype inferred from the attribute Java type.

  • related - defines the array of related persistent attributes to be fetched from the database when this property is included in a view.

Field example:

@Transient
@MetaProperty
protected String token;

Method example:

@MetaProperty
public String getLocValue() {
    if (!StringUtils.isEmpty(messagesPack)) {
        return AppBeans.get(Messsages.class).getMessage(messagesPack, value);
    } else {
        return value;
    }
}
@OnDelete

Determines related entities handling policy in case of soft deletion of the entity, containing the attribute. See Soft Deletion.

Example:

@OneToMany(mappedBy = "group")
@OnDelete(DeletePolicy.CASCADE)
private Set<Constraint> constraints;
@OnDeleteInverse

Determines related entities handling policy in case of soft deletion of the entity from the inverse side of the relationship. See Soft Deletion.

Example:

@ManyToOne
@JoinColumn(name = "DRIVER_ID")
@OnDeleteInverse(DeletePolicy.DENY)
private Driver driver;
@Composition

Indicates that the relationship is a composition, which is a stronger variant of the association. Essentially this means that the related entity should only exist as a part of the owning entity, i.e. be created and deleted together with it.

For example, a list of items in an order (Order class contains a collection of Item instances):

@OneToMany(mappedBy = "order")
@Composition
protected List<Item> items;

Choosing @Composition annotation as the relationship type enables making use of a special commit mode for datasources in edit screens. In this mode, the changes to related instances are only stored when the master entity is committed. See Editing Composite Entities for details.

@LocalizedValue

Determines a method for retrieving a localized value for an attribute, using MessageTools`.getLocValue()` method.

Parameters:

  • messagePack – explicit indication of the package, from which a localized message will be taken, for example, com.haulmont.cuba.core.entity.

  • messagePackExpr – expression defining the path to the attribute, containing a package name from which the localized message should be taken (for example, proc.messagesPack). The path starts from the attribute of the current entity.

The annotation in the example below indicates that localized message for the state attribute value should be taken from the package name defined in the messagesPack attribute of the proc entity.

@Column(name = "STATE")
@LocalizedValue(messagePackExpr = "proc.messagesPack")
protected String state;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "PROC_ID")
protected Proc proc;
@IgnoreUserTimeZone

Makes the platform to ignore the user’s time zone (if it is set for the current session) for an attribute of the timestamp type (annotated with @javax.persistence.Temporal.TIMESTAMP).

4.2.1.3. Enum Attributes

The standard use of JPA for enum attributes involves an integer database field containing a value obtained from the ordinal() method. This approach may lead to the following issues with extending a system in production:

  • An entity instance cannot be loaded, if the value of the enum in the database does not equal to any ordinal value.

  • It is impossible to add a new value between the existing ones, which is important when sorting by enumeration value (order by).

CUBA-style approach to solving these problems is to detach the value stored in the database from ordinal value of the enumeration. In order to do this, the field of the entity should be declared with the type, stored in the database (Integer or String), while the access methods (getter / setter) should be created with the actual enumeration type.

Example:

@Entity(name = "sales$Customer")
@Table(name = "SALES_CUSTOMER")
public class Customer extends StandardEntity {

    @Column(name = "GRADE")
    protected Integer grade;

    public CustomerGrade getGrade() {
        return grade == null ? null : CustomerGrade.fromId(grade);
    }

    public void setGrade(CustomerGrade grade) {
        this.grade = grade == null ? null : grade.getId();
    }
    ...
}

In this case, the enumeration class can look like this:

public enum CustomerGrade implements EnumClass<Integer> {

    PREMIUM(10),
    HIGH(20),
    MEDIUM(30);

    private Integer id;

    CustomerGrade(Integer id) {
        this.id = id;
    }

    @Override
    public Integer getId() {
        return id;
    }

    public static CustomerGrade fromId(Integer id) {
        for (CustomerGrade grade : CustomerGrade.values()) {
            if (grade.getId().equals(id))
                return grade;
        }
        return null;
    }
}

For correct reflection in metadata, the enumeration class must implement the EnumClass interface.

As the examples show, grade attribute corresponds to the Integer type value stored in the database, which is specified by the id field of CustomerGrade enumeration, namely 10, 20 or 30. At the same time, the application code and metadata framework use CustomerGrade enum through access methods, which perform the actual conversion.

A call to getGrade() method will simply return null, if the value in the database does not correspond to any of the enumeration values. In order to add a new value, for example, HIGHER, between HIGH and PREMIUM, it is sufficient to add new enumeration value with id = 15, which ensures that sorting by Customer.grade field remains correct.

Enumeration values can be associated with localized names that will be displayed in the user interface of the application.

4.2.1.4. Soft Deletion

CUBA platform supports soft deletion mode, when the records are not deleted from the database, but instead, marked in a special way, so that they become inaccessible for common use. Later, these records can be either completely removed from the database using some kind of scheduled procedure or restored.

Soft deletion mechanism is transparent for an application developer, the only requirement is for entity class to implement SoftDelete interface. The platform will adjust data operations automatically.

Soft deletion mode offers the following benefits:

  • Significantly reduces the risk of data loss caused by incorrect user actions.

  • Enables making certain records inaccessible instantly even if there are references to them.

    Using Orders-Customers data model as an example, let’s assume that a certain customer has made several orders but we need to make the customer instance inaccessible for users. This is impossible with traditional hard deletion, as deletion of a customer instance requires either deletion of all related orders or setting to null all references to the customer (meaning data loss). After soft deletion, the customer instance becomes unavailable for search and modification; however, a user can see the name of the customer in the order editor, as deletion attribute is purposely ignored when the related entities are fetched.

    The standard behavior above can be modified with related entities processing policy.

The negative impact of soft deletion is increase in database size and likely need for additional cleanup procedures.

4.2.1.4.1. Use of Soft Deletion

To support soft deletion, the entity class should implement SoftDelete interface, and the corresponding database table should contain the following columns:

  • DELETE_TS – when the record was deleted.

  • DELETED_BY – the login of the user who deleted the record.

The default behavior for instances implementing SoftDelete interface, is that soft deleted entities are not returned by queries or search by id. If required, this behavior can by dynamically turned off using the following methods:

  • Calling setSoftDeletion(false) for the current EntityManager instance.

  • Calling setSoftDeletion(false) for LoadContext object when requesting data via DataManager.

  • On the datasource level – calling CollectionDatasource.setSoftDeletion(false) or setting softDeletion="false" attribute of collectionDatasource element in the screen’s XML-descriptor.

In soft deletion mode, the platform automatically filters out the deleted instances when loading by identifier and when using JPQL queries, as well as the deleted elements of the related entities in collection attributes. However, related entities in single-value (*ToOne) attributes are loaded, regardless of whether the related instance was deleted or not.

4.2.1.4.2. Related Entities Processing Policy

The platform offers a mechanism for managing related entities when deleting, which is largely similar to ON DELETE rules for database foreign keys. This mechanism works on the middle tier and uses @OnDelete, @OnDeleteInverse annotations on entity attributes.

@OnDelete annotation is processed when the entity in which this annotation is found is deleted, but not the one pointed to by this annotation (this is the main difference from cascade deletion at the database level).

@OnDeleteInverse annotation is processed when the entity which it points to is deleted (which is similar to cascade deletion at foreign key level in the database). This annotation is useful when the object being deleted has no attribute that can be checked before deletion. Typically, the object being checked has a reference to the object being deleted, and this is the attribute that should be annotated with @OnDeleteInverse.

Annotation value can be:

  • DeletePolicy.DENY – prohibits entity deletion, if the annotated attribute is not null or not an empty collection.

  • DeletePolicy.CASCADE – cascade deletion of the annotated attribute.

  • DeletePolicy.UNLINK – disconnect the link with the annotated attribute. It is reasonable to disconnect the link only in the owner side of the association – the one with @JoinColumn annotation in the entity class.

Examples:

  1. Prohibit deletion of entity with references: DeletePolicyException will be thrown if you try to delete Customer instance, which is referred to by at least one Order.

    Order.java

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CUSTOMER_ID")
    @OnDeleteInverse(DeletePolicy.DENY)
    protected Customer customer;

    Customer.java

    @OneToMany(mappedBy = "customer")
    protected List<Order> orders;
  2. Cascade deletion of related collection elements: deletion of Role instance causes all Permission instances to be deleted as well.

    Role.java

    @OneToMany(mappedBy = "role")
    @OnDelete(DeletePolicy.CASCADE)
    protected Set<Permission> permissions;

    Permission.java

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ROLE_ID")
    protected Role role;
  3. Disconnect the links with related collection elements: deletion of Role instance leads to setting to null references to this Role for all Permission instances included in the collection.

    Role.java

    @OneToMany(mappedBy = "role")
    protected Set<Permission> permissions;

    Permission.java

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ROLE_ID")
    @OnDeleteInverse(DeletePolicy.UNLINK)
    protected Role role;

Implementation notes:

  1. Be careful when using @OnDeleteInverse together with CASCADE and UNLINK policies. During this process, all instances of the related objects are fetched from the database, modified and then saved.

    For example, if @OnDeleteInverse(CASCADE) policy is set on Job.customer attribute in a CustomerJob association with many jobs to one customer, if you set @OnDeleteInverse(CASCADE) policy on Job.customer attribute, all jobs will be retrieved and modified when deleting a Customer instance. This may overload the application server or the database.

    On the other hand, using @OnDeleteInverse(DENY) is safe, as it only involves counting the number of the related objects. If there are more than 0, an exception is thrown. This makes use of @OnDeleteInverse(DENY) suitable for Job.customer attribute.

  2. Related entities processing is implemented at Middleware using Entity Listeners.

4.2.1.4.3. Unique Constraints at Database Level

In order to apply unique constraints for certain value in the soft deletion mode, at least one non-deleted record with this value and an arbitrary number of deleted records with the same value may exist in database.

This logic can be implemented in a specific way for each database server type:

  • If database server supports partial indexes (e.g. PostgreSQL), unique restrictions can be achieved as follows:

    create unique index IDX_SEC_USER_UNIQ_LOGIN on SEC_USER (LOGIN_LC) where DELETE_TS is null
  • If database server does not support partial indexes (e.g. Microsoft SQL Server 2005), DELETE_TS field can be included in the unique index:

    create unique index IDX_SEC_USER_UNIQ_LOGIN on SEC_USER (LOGIN_LC, DELETE_TS)

4.2.2. Metadata Framework

Metadata framework is used to support efficient work with data model in CUBA-applications. The framework:

  • provides API for obtaining information about entities, their attributes and relations between the entities; it is also used for traversing object graphs;

  • serves as a specialized and more convenient alternative for Java Reflection API;

  • controls permitted data types and relationships between entities;

  • enables implementation of universal mechanisms for operations with data.

4.2.2.1. Metadata Interfaces

Let us consider the basic metadata interfaces.

MetadataFramework
Figure 10. Metadata Framework Interfaces
Session

Entry point of the metadata framework. Enables obtaining MetaClass instances by name and by the corresponding Java class. Note the difference in methods: getClass() methods can return null while getClassNN() (Non Null) methods cannot.

Session object can be obtained using the Metadata infrastructure interface.

Example:

@Inject
protected Metadata metadata;
...
Session session = metadata.getSession();
MetaClass metaClass1 = session.getClassNN("sec$User");
MetaClass metaClass2 = session.getClassNN(User.class);
assert metaClass1 == metaClass2;
MetaModel

Rarely used interface intended to group meta-classes.

Meta-classes are grouped by the root name of Java project package specified in metadata.xml file.

MetaClass

Entity class metadata interface. MetaClass is always associated with the Java class which it represents.

Basic methods:

  • getName() – entity name, according to convention the first part of the name before $ sign is the namespace code, for example, sales$Customer.

  • getProperties() – the list of meta-properties (MetaProperty).

  • getProperty(), getPropertyNN() – methods return meta-properties by name. If there is no attribute with provided name, the first method returns null, and the second throws an exception.

    Example:

    MetaClass userClass = session.getClassNN(User.class);
    MetaProperty groupProperty = userClass.getPropertyNN("group");
  • getPropertyPath() – allows you to navigate by references. This method accepts string parameter – path in the format of dot-separated attribute names. The returned MetaPropertyPath object enables accessing the required (the last in the path) attribute by invoking getMetaProperty() method.

    Example:

    MetaClass userClass = session.getClassNN(User.class);
    MetaProperty groupNameProp = userClass.getPropertyPath("group.name").getMetaProperty();
    assert groupNameProp.getDomain().getName().equals("sec$Group");
  • getJavaClass() – entity class, corresponding to this MetaClass.

  • getAnnotations() – collection of meta-annotations.

MetaProperty

Entity attribute metadata interface.

Basic methods:

  • getName() – property name, corresponds to entity attribute name.

  • getDomain() – meta-class, owning this property.

  • getType()- the property type:

    • simple type: DATATYPE

    • enumeration: ENUM

    • reference type of two kinds:

      • ASSOCIATION − simple reference to another entity. For example, Order-Customer relationship is an association.

      • COMPOSITION − reference to the entity, having no consistent value without the owning entity. COMPOSITION is considered to be a "closer" relationship than ASSOCIATION. For example, the relationship between Order and its Items is a COMPOSITION, as the Item cannot exist without the Order to which it belongs.

        The type of ASSOCIATION or COMPOSITION reference attributes affects entity edit mode: in the first case the related entity is persisted to the database independently, in the second case – only together with the owning entity. See Editing Composite Entities for details.

  • getRange()Range interface providing detailed description of the attribute type.

  • isMandatory() – indicates a mandatory attribute. For instance, it is used by visual components to signal a user that value is mandatory.

  • isReadOnly() – indicates a read-only attribute.

  • getInverse() – for reference-type attribute, returns the meta-property from the other side of the association, if such exists.

  • getAnnotatedElement() – field (java.lang.reflect.Field) or method (java.lang.reflect.Method), corresponding to the entity attribute.

  • getJavaType() – Java class of the entity attribute. It can either be the type of corresponding field or the type of the value returned by corresponding method.

  • getDeclaringClass() – Java class containing this attribute.

    Range

    Interface describing entity attribute type in detail.

    Basic methods:

  • isDatatype() – returns true for simple type attribute.

  • asDatatype() – returns Datatype for simple type attribute.

  • isEnum() – returns true for enumeration type attribute.

  • asEnumeration() – returns Enumeration for enumeration type attribute.

  • isClass() – returns true for reference attribute of ASSOCIATION or COMPOSITION type.

  • asClass() – returns metaclass of associated entity for a reference attribute.

  • isOrdered() – returns true if the attribute is represented by an ordered collection (for example List).

  • getCardinality() – relation kind of the reference attribute: ONE_TO_ONE, MANY_TO_ONE, ONE_TO_MANY, MANY_TO_MANY.

4.2.2.2. Metadata Building

The main source for metadata structure generation are annotated entity classes.

Entity class will be present in the metadata in the following cases:

  • Persistent entity class is annotated by @Entity, @Embeddable, @MappedSuperclass and is located within the root package specified in metadata.xml.

  • Non-persistent entity class is annotated by @MetaClass and is located within the root package specified in metadata.xml.

All entities inside same root package are put into the same MetaModel instance, which is given the name of this package. Entities within the same MetaModel can contain arbitrary references to each other. References between entities from different meta-models can be created in the order of declaration of metadata.xml files in cuba.metadataConfig property.

Entity attribute will be present in metadata if:

  • A class field is annotated by @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany, @Embedded.

  • A class field or an access method (getter) is annotated by @MetaProperty.

Metaclass and metaproperty parameters are determined on the base of the listed annotations parameters as well as field types and class methods. Besides, if an attribute does not have write access method (setter), it becomes immutable (read only).

4.2.2.3. Datatype

Datatype interface describes a valid data type for the entity attribute if it is not a reference. Each Datatype implementation corresponds to a single Java class.

All of the instances are registered in repository – Datatypes class, which performs loading and initializing of Datatype implementation classes in the following way:

  • datatypes.xml file is searched in CLASSPATH root, and if it is found, Datatypes repository is initialized from it.

  • otherwise Datatypes repository is initialized from /com/haulmont/chile/core/datatypes/datatypes.xml file.

Datatype instance can be obtained in two ways:

  • For an entity attribute – from the corresponding meta-property using getRange().asDatatype() call.

  • Using Datatypes.get() static method by passing to it the name of the Datatype implementation or Java class it was created for.

Datatypes are associated with entity attributes during application start according to the following rules:

  • If @MetaProperty annotation is defined on the field or method having a non-empty datatype value, the attribute is associated with the Datatype instance with the given name.

    For instance, if the entity attribute is declared as in the example below, it will be associated with a nonstandard type – GeoCoordinateDatatype:

    @MetaProperty(datatype = GeoCoordinateDatatype.NAME)
    @Column(name = "LATITUDE")
    private Double latitude;
  • In most cases, explicit specification is omitted, and the attribute is associated with the Datatype instance from repository, which is returned by Datatypes.get(Class) by supplied field or method type.

    In example below, latitude attribute will get a standard DoubleDatatype type registered in the /com/haulmont/chile/core/datatypes/datatypes.xml base file:

    @Column(name = "LATITUDE")
    private Double latitude;

Basic methods of Datatype interfaces:

  • getName() – returns the unique name of the implementation.

  • format() – converts the passed value into a string.

  • parse() – transforms a string into the value of corresponding type.

Datatype determines two sets of methods for formatting and parsing: considering and not considering locale. Conversion considering locale is applied everywhere in user interface, ignoring locale – in system mechanisms, for example, serialization in REST API.

Parsing formats ignoring locale are specified in the above mentioned datatypes.xml file.

The parsing formats considering locale are provided in the main localized messages pack, in the strings containing the following keys:

  • numberDecimalSeparator – specifies decimal separator for numeric types.

  • numberGroupingSeparator – defines separator between digits groups for numeric types (e.g. when space is used as separator, number will be formatted as 1 000 000).

  • integerFormat – format for Integer and Long types.

  • doubleFormat – format for Double type.

  • decimalFormat – format for `BigDecimal `type.

  • dateTimeFormat – format for java.util.Date type.

  • dateFormat – format for java.sql.Date type.

  • timeFormat – format for java.sql.Time type.

  • trueString – string corresponding to Boolean.TRUE.

  • falseString – string corresponding to Boolean.FALSE.

All the listed formats are specified in the main localized message pack of CUBA base projects by default, and can be overridden in the similar files of the application project.

4.2.2.3.1. Example of Data Formatting in UI

Let us consider the way Order.date attribute is displayed in orders browser table.

order-browse.xml

<table id="ordersTable">
    ...
    <columns>
        <column id="date"/>
        ...

date attribute in Order class is defined using "date" type:

@Column(name = "DATE", nullable = false)
@Temporal(TemporalType.DATE)
private Date date;

If the current user is logged in with the Russian locale, the following string is retrieved from the main message pack on the client tier:

dateFormat=dd.MM.yyyy

The result: date "6th August 2012 " is converted into a string "06.08.2012" which is displayed in the table cell.

4.2.2.3.2. Examples of Date and Number Formatting in the Application Code
  • Date formatting example

    @Inject
    protected UserSessionSource userSessionSource;
    ...
    Date date = ...;
    String dateStr = Datatypes.getNN(Date.class).format(date, userSessionSource.getLocale());
  • Example of formatting of numeric values with high accuracy (up to 5 decimal places) in Web Client:

    /com/sample/sales/web/messages_ru.properties

    coordinateFormat = #,##0.00000

    SomeClass.java

    @Inject
    protected Messages messages;
    @Inject
    protected UserSessionSource userSessionSource;
    ...
    String coordinateFormat = messages.getMainMessage("coordinateFormat");
    FormatStrings formatStrings = Datatypes.getFormatStrings(userSessionSource.getLocale());
    NumberFormat format = new DecimalFormat(coordinateFormat, formatStrings.getFormatSymbols());
    
    String formattedValue = format.format(value);
4.2.2.3.3. Example of a Custom Datatype

Let us consider the implementation of a custom GeoCoordinateDatatype, intended for the attributes storing geographical coordinates.

First, create the following class in the global module:

package com.company.sample;

import com.haulmont.chile.core.datatypes.Datatypes;
import com.haulmont.chile.core.datatypes.FormatStrings;
import com.haulmont.chile.core.datatypes.impl.DoubleDatatype;
import org.apache.commons.lang.StringUtils;
import org.dom4j.Element;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

public class GeoCoordinateDatatype extends DoubleDatatype {

    // This field is required for Studio even if you don't use it in code
    public static final String NAME = "geocoordinate";

    // The format is the same for all locales but may differ in decimal points
    public static final String FORMAT = "#0.000000";

    public GeoCoordinateDatatype(Element element) {
        super(element);
    }

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    public String format(Object value, Locale locale) {
        if (value == null)
            return "";
        FormatStrings formatStrings = Datatypes.getFormatStrings(locale);
        if (formatStrings == null)
            return format(value);

        NumberFormat format = new DecimalFormat(FORMAT, formatStrings.getFormatSymbols());
        return format.format(value);
    }

    @Override
    public Double parse(String value, Locale locale) throws ParseException {
        if (StringUtils.isBlank(value))
            return null;
        FormatStrings formatStrings = Datatypes.getFormatStrings(locale);
        if (formatStrings == null)
            return parse(value);

        NumberFormat format = new DecimalFormat(FORMAT, formatStrings.getFormatSymbols());
        return parse(value, format).doubleValue();
    }
}

Next, create the datatypes.xml file in the root of the global module src directory in the application project and copy contents from the /com/haulmont/chile/core/datatypes/datatypes.xml file located in global module of the cuba base project. Then add registration of the new type to it:

<datatypes>

    <datatype class="com.company.sample.GeoCoordinateDatatype"
              format="#0.000000" decimalSeparator="." groupingSeparator=""
              javaType="java.lang.Double"/>

    <!-- copied from /com/haulmont/chile/core/datatypes/datatypes.xml -->

    <datatype class="com.haulmont.chile.core.datatypes.impl.BooleanDatatype"/>
    <datatype class="com.haulmont.chile.core.datatypes.impl.IntegerDatatype"
              format="0" groupingSeparator=""/>
    <datatype class="com.haulmont.chile.core.datatypes.impl.LongDatatype"
              format="0" groupingSeparator=""/>
    <datatype class="com.haulmont.chile.core.datatypes.impl.DoubleDatatype"
              format="0.###" decimalSeparator="." groupingSeparator=""/>
    <datatype class="com.haulmont.chile.core.datatypes.impl.BigDecimalDatatype"
              format="0.####" decimalSeparator="." groupingSeparator=""/>
    <datatype class="com.haulmont.chile.core.datatypes.impl.StringDatatype"/>
    <datatype class="com.haulmont.chile.core.datatypes.impl.DateTimeDatatype"
              format="yyyy-MM-dd HH:mm:ss.SSS"/>
    <datatype class="com.haulmont.chile.core.datatypes.impl.DateDatatype"
              format="yyyy-MM-dd"/>
    <datatype class="com.haulmont.chile.core.datatypes.impl.TimeDatatype"
              format="HH:mm:ss"/>
    <datatype class="com.haulmont.chile.core.datatypes.impl.UUIDDatatype"/>
    <datatype class="com.haulmont.chile.core.datatypes.impl.ByteArrayDatatype"/>

</datatypes>

In the datatype element, you can also specify the sqlType attribute containing an SQL type of your database suitable for storing values of the new type. This SQL type will be used by CUBA Studio when it generates database scripts. Studio can automatically determine an SQL type for the following Java types:

  • java.lang.Boolean

  • java.lang.Integer

  • java.lang.Long

  • java.math.BigDecimal

  • java.lang.Double

  • java.lang.String

  • java.util.Date

  • java.util.UUID

  • byte[]

If you have specified one of the listed Java types in the javaType attribute, the sqlType attribute can be omitted.

Finally, specify the new datatype for the required attributes (the new data type will also be available in the Studio entity attribute editor):

@MetaProperty(datatype = GeoCoordinateDatatype.NAME)
@Column(name = "LATITUDE")
private Double latitude;

After the above listed operations are completed, the latitude attribute will be displayed in the desired format throughout the application.

4.2.2.4. Meta-Annotations

Entity meta-annotations are a set of key/value pairs providing additional information about entities.

Meta-annotations are accessed using meta-class getAnnotations() method.

The sources of meta-annotations are:

  • @OnDelete, @OnDeleteInverse, @Extends annotations. These annotations cause creation of special meta-annotations for describing relations between entities.

  • @NamePattern, @SystemLevel, @EnableRestore, @TrackEditScreenHistory annotations. These annotations cause generation of meta-annotations with keys corresponding to the full name of Java class of the annotation.

  • Optional: custom annotations can be defined in a project, and reflected to corresponding meta-annotations in the overridden MetadataImpl.initMetaAnnotations() method.

  • Optional: entity meta-annotations can also be defined in metadata.xml files. If a meta-annotation in XML has the same name as the meta-annotation created by Java entity class annotation, then it will override the latter.

    The example below shows meta-annotations definition in metadata.xml:

    <annotations>
        <entity class="com.haulmont.cuba.security.entity.User">
            <annotation name="com.haulmont.cuba.core.entity.annotation.TrackEditScreenHistory" value="false"/>
            <annotation name="com.haulmont.cuba.core.entity.annotation.EnableRestore" value="true"/>
        </entity>
    </annotations>

4.2.3. Views

When retrieving entities from the database, we often face the question: how to ensure loading of related entities to the desired depth?

For example, you need to display the date and amount together with the Customer name in the Orders browser, which means that you need to fetch the related Customer instance. And for the Order editor screen, you need to fetch the collection of Items, in addition to that each Item should contain a related Product instance to display its name.

Lazy loading can not help in most cases because data processing is usually performed not in the transaction where the entities were loaded but, for example, on the client tier in UI. At the same time, it is unacceptable to apply eager fetching using entity annotations as it leads to constant retrieval of the entire graph of related entities which can be very large.

Another similar problem is the requirement to limit the set of local entity attributes of the loaded graph: for example, some entity can have 50 attributes, including BLOB, but only 10 attributes need to be displayed on the screen. In this case, why should we download 40 remaining attributes from the database, then serialize them and transfer to the client when it does not need them at the moment?

Views mechanism resolves these issues by retrieving from the database and transmitting to the client entity graphs limited by depth and by attributes. A view is a descriptor of the object graph required for a certain UI screen or data-processing operation.

Views processing is performed in the following way:

  • All relations in the data model are declared with lazy fetching property (fetch = FetchType.LAZY. See Entity Annotations).

  • In the data loading process, the calling code provides required view together with JPQL query or entity identifier.

  • The so-called FetchGroup is produced on the base of the view – this is a special feature of EclipseLink framework lying in the base of the ORM layer. FetchGroup affects the generation of SQL queries to the database: both the list of returned fields and joins with other tables containing related entities.

View
Figure 11. View Classes

A view is determined by an instance of the View class, where:

  • entityClass – the entity class, for which the view is defined. In other words, it is the "root" of the loaded entities tree.

  • name – the name of the view. It should be either null or a unique name within all views for the entity.

  • properties – collection of ViewProperty instances corresponding to the entity attributes that should be loaded.

  • includeSystemProperties – if set, system attributes (defined by basic interfaces of persistent entities, such as BaseEntity and Updatable) are automatically included in the view.

ViewProperty class has the following properties:

  • name – the name of the entity attribute.

  • view – for reference attributes, specifies the view which which will be used to load the related entity.

  • fetch - for reference attributes, specifies how to fetch the related entity from the database. It corresponds to the FetchMode enum and can have one of the following values:

    • AUTO - the platform will choose an optimal mode depending on the relation type.

    • UNDEFINED - fetching will be performed according to JPA rules, which effectively means loading by a separate select.

    • JOIN - fetching in the same select by joining with referenced table.

    • BATCH - queries of related objects will be optimized in batches. See more here.

    If the fetch attribute is not specified, the AUTO mode is applied. If the reference represents a cacheable entity, UNDEFINED will be used regardless of the value specified in the view.

Tip

Regardless of the attributes defined in the view, the following attributes are always loaded:

  • id – entity identifier.

  • version – used for optimistic locking of the entities implementing Versioned.

  • deleteTs, deletedBy – used for the entities, implementing SoftDelete.

Warning

An attempt to get or set a value for a not loaded attribute (not included into a view) raises an exception. You can check whether the attribute was loaded using the PersistenceHelper.isLoaded() method.

4.2.3.1. Views Creation

A view can be created in two possible ways:

  • Programmatically – by creating a View instance, for example:

    View view = new View(Order.class)
            .addProperty("date")
            .addProperty("amount")
            .addProperty("customer", new View(Customer.class)
                .addProperty("name")
            );

    Typically, this way can be appropriate for creating views that are used in a single piece of business logic.

  • Declaratively – by creating an XML descriptor and deploying it to ViewRepository. View instances are created and cached when the XML descriptor is deployed. Further on, the required view can be retrieved in any part of the application code by a call to ViewRepository providing the entity class and the view name.

Let us consider in details the declarative way for creation and working with views.

ViewRepository is a Spring bean, accessible to all application blocks. The reference to ViewRepository can be obtained using injection or through the Metadata infrastructure interface. ViewRepository.getView() methods are used to retrieve view instances from the repository. deployViews() methods from AbstractViewRepository basic implementation are used to deploy XML descriptors to the repository.

Two views named _local and _minimal are available in the views repository for each entity by default:

  • _local contains all local entity attributes.

  • _minimal contains the attributes which are included to the name of the entity instance and specified in the @NamePattern annotation. If the @NamePattern annotation is not specified at the entity, this view does not contain any attributes.

The detailed structure of view XML descriptors is explained here.

The example below shows a view descriptor for the Order entity which provides loading of all local attributes, associated Customer and the Items collection:

<view class="com.sample.sales.entity.Order"
      name="orderWithCustomer"
      extends="_local">
    <property name="customer" view="_minimal"/>
    <property name="items" view="itemInOrder"/>
</view>

The recommended way of grouping and deployment of view descriptors is as follows:

  • Create views.xml file in the src root of the global module and place all view descriptors that should be globally accessible (i.e. on all application tiers) into it.

  • Register this file in the [cuba.viewsConfig] application property of all blocks, i.e. in app.properties of the core module, web-app.properties of the web module, etc. This will ensure automatic deployment of the views upon application startup into the repository.

  • If there are views which are used only in one application block, they can be specified in the similar file of this block, for example, web-views.xml, and registered in [cuba.viewsConfig] property of this block only.

    If the repository contains a view with certain name for some entity, an attempt to deploy another view with this name for the same entity will be ignored. If you need to replace the existing view in the repository with a new one and guarantee its deployment, specify overwrite = "true" attribute for it.

Tip

It is recommended to give descriptive names to the views. For example, not just "browse", but "customerBrowse". It simplifies the search of views in XML descriptors.

4.2.4. Managed Beans

Managed Beans are program components intended for implementation of the application’s business logic. "Managed" in this case means that the instance creation and dependency management is handled by the container, which is the main part of the Spring framework.

Tip

Managed Bean is a singleton by default, i.e., only one instance of such class exists in each application block. If a singleton bean contains mutable data in fields (in other words, has a state), it is necessary to synchronize access to such data.

4.2.4.1. Creating a Bean

To create a managed bean, add the @org.springframework.stereotype.Component annotation to the Java class. For example:

package com.sample.sales.core;

import com.sample.sales.entity.Order;
import org.springframework.stereotype.Component;

@Component(OrderWorker.NAME)
public class OrderWorker {
    public static final String NAME = "sales_OrderWorker";

    public void calculateTotals(Order order) {
    }
}

It is recommended to assign a unique name to the bean in the {project_name}_{class_name} form and to define it in the NAME constant.

Tip

The @javax.annotation.ManagedBean can also be used for the managed bean definition, but it can cause problems when deploying the application into some application servers. Therefore we recommend to use only @Component annotation from Spring Framework.

The managed bean class should be placed inside the package tree with the root specified in the context:component-scan element of the spring.xml file. In this case, the spring.xml file contains the element:

<context:component-scan base-package="com.sample.sales"/>

which means that the search for annotated beans for this application block will be performed starting with the com.sample.sales package.

Managed beans can be created on any tier, because the Spring Framework container is used in all standard blocks of the application.

4.2.4.2. Using the Bean

A reference to the bean can be obtained through injection or through the AppBeans class. As an example of using the bean, let us look at the implementation of the OrderService bean that delegates the execution to the OrderWorker bean:

package com.sample.sales.core;

import com.haulmont.cuba.core.Persistence;
import com.sample.sales.entity.Order;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.inject.Inject;

@Service(OrderService.NAME)
public class OrderServiceBean implements OrderService {

    @Inject
    protected Persistence persistence;

    @Inject
    protected OrderWorker orderWorker;

    @Transactional
    @Override
    public BigDecimal calculateTotals(Order order) {
        Order entity = persistence.getEntityManager().merge(order);
        orderWorker.calculateTotals(entity);
    }
}

In this example, the service starts a transaction, merges the detached entity obtained from the client level into the persistent context, and passes the control to the OrderWorker bean, which contains the main business logic.

4.2.5. JMX Beans

Sometimes, it is necessary to give system administrator an ability to view and change the state of some managed bean at runtime. In such case, it is recommended to create a JMX bean – a program component having the JMX interface. JMX bean is usually a wrapper delegating calls to the managed bean which actually maintains state: cache, configuration data or statistics.

JMXBeans
Figure 12. JMX Bean Class Diagram

As you can see from the diagram, the JMX bean consists of the interface and the implementation class. The class should be a managed bean, i.e., should have the @Component annotation and unique name. The interface of the JMX bean is registered in spring.xml in a special way to create the JMX interface in the current JVM.

Calls to all JMX bean interface methods are intercepted using Spring AOP by the MBeanInterceptor interceptor class, which sets the correct ClassLoader in the current thread and enables logging of unhandled exceptions.

Warning

The JMX bean interface name must conform to the following format: {class_name}MBean.

JMX-interface can be utilized by external tools, such as jconsole or jvisualvm. In addition, the Web Client platform block includes the JMX console, which provides the basic tools to view the status and call the methods of the JMX beans.

4.2.5.1. Creating a JMX Bean

The following example shows how to create a JMX bean.

  • JMX bean interface:

    package com.sample.sales.core;
    
    import org.springframework.jmx.export.annotation.*;
    
    @ManagedResource(description = "Performs operations on Orders")
    public interface OrdersMBean {
    
        @ManagedOperation(description = "Recalculates an order amount")
        @ManagedOperationParameters({@ManagedOperationParameter(name = "orderId", description = "")})
        String calculateTotals(String orderId);
    }

    The interface and its methods may contain annotations to specify the description of the JMX bean and its operations. This description will be displayed in all tools that work with this JMX interface, thereby helping the system administrator.

    Since the JMX tools support a limited set of data types, it is desirable to use String as the type for the parameters and result of the method and perform the conversion inside the method, if necessary.

  • The JMX bean class:

    package com.sample.sales.core;
    
    import com.haulmont.cuba.core.*;
    import com.haulmont.cuba.core.app.*;
    import com.sample.sales.entity.Order;
    import org.apache.commons.lang.exception.ExceptionUtils;
    import org.springframework.stereotype.Component;
    import javax.inject.Inject;
    import java.util.UUID;
    
    @Component("sales_OrdersMBean")
    public class Orders implements OrdersMBean {
    
        @Inject
        protected OrderWorker orderWorker;
    
        @Inject
        protected Persistence persistence;
    
        @Authenticated
        @Override
        public String calculateTotals(String orderId) {
            try {
                try (Transaction tx = persistence.createTransaction()) {
                    Order entity = persistence.getEntityManager().find(Order.class, UUID.fromString(orderId));
                    orderWorker.calculateTotals(entity);
                    tx.commit();
                };
                return "Done";
            } catch (Throwable e) {
                return ExceptionUtils.getStackTrace(e);
            }
        }
    }

    The @Component annotation defines the class as a managed bean with the sales_OrdersMBean name. The name is specified directly in the annotation and not in the constant, since access to the JMX bean from Java code is not required.

    Lets overview the implementation of the calculateTotals() method.

    • The method has the @Authenticated annotation, i.e., system authentication is performed on method entry in the absence of the user session.

    • The method’s body is wrapped in the try/catch block, so that, if successful, the method returns "Done", and in case of error – the stack trace of the exception as string.

      In this case, all exceptions are handled and therefore do not get logged automatically, because they never fall through to MBeanInterceptor. If logging of exceptions is required, the call of the logger should be added in the catch section.

    • The method starts the transaction, loads the Order entity instance by identifier, and passes control to the OrderWorker bean for processing.

  • The registration of the JMX bean in spring.xml:

    <bean id="sales_MBeanExporter" lazy-init="false"
          class="com.haulmont.cuba.core.sys.jmx.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="${cuba.webContextName}.sales:type=Orders"
                       value-ref="sales_OrdersMBean"/>
            </map>
        </property>
    </bean>

All JMX beans of a project are declared in one MBeanExporter instance in the map/entry elements of the beans property. The key is JMX ObjectName, the value – the bean’s name specified in the @Component annotation. ObjectName begins with the name of the web application, because several web applications, which export the same JMX interfaces, can be deployed into one application server instance (i.e., into one JVM).

4.2.5.2. The Platform JMX Beans

This section describes some of the JMX beans available in the platform.

4.2.5.2.1. CachingFacadeMBean

CachingFacadeMBean provides methods to clear various caches in the Middleware and Web Client blocks.

JMX ObjectName: app-core.cuba:type=CachingFacade and app.cuba:type=CachingFacade

4.2.5.2.2. ConfigStorageMBean

ConfigStorageMBean enables viewing and setting values of the application properties in the Middleware, Web Client and Web Portal blocks.

This interface has separate sets of operations for working with properties stored in files (*AppProperties) and stored in the database (*DbProperties). These operations show only the properties explicitly set in the storage. It means that if you have a configuration interface defining a property and its default value, but you did not set the value in the database (or a file), these methods will not show the property and its current value.

Please note that the changes to property values stored in files are not persistent, and are valid only until restart of the application block.

Unlike the operations described above, the getConfigValue() operation returns exactly the same value as the corresponding method of the configuration interface invoked in the application code.

JMX ObjectName:

  • app-core.cuba:type=ConfigStorage

  • app.cuba:type=ConfigStorage

  • app-portal.cuba:type=ConfigStorage

4.2.5.2.3. EmailerMBean

EmailerMBean enables viewing the current values of the email sending parameters, and sending test messages.

JMX ObjectName: app-core.cuba:type=Emailer

4.2.5.2.4. PersistenceManagerMBean

PersistenceManagerMBean provides the following abilities:

  • Managing entity statistics mechanism.

  • Viewing new DB update scripts using the findUpdateDatabaseScripts() method. Triggering DB update with the updateDatabase() method.

  • Executing arbitrary JPQL queries in the Middleware context by using jpqlLoadList(), jpqlExecuteUpdate() methods.

JMX ObjectName: app-core.cuba:type=PersistenceManager

4.2.5.2.5. ScriptingManagerMBean

ScriptingManagerMBean is the JMX facade for the Scripting infrastructure interface.

JMX ObjectName: app-core.cuba:type=ScriptingManager

JMX attributes:

JMX operations:

  • runGroovyScript() – executes a Groovy script in the Middleware context and returns the result. The following variables are passed to the script:

    • persistence of the Persistence type.

    • metadata of the Metadata type.

    • configuration of the Configuration type.

      The result type should be of the String type to be displayed in the JMX interface. Otherwise, the method is similar to the Scripting.runGroovyScript() method.

      The example script for creating a set of test users is shown below:

      import com.haulmont.cuba.core.*
      import com.haulmont.cuba.core.global.*
      import com.haulmont.cuba.security.entity.*
      
      PasswordEncryption passwordEncryption = AppBeans.get(PasswordEncryption.class)
      
      Transaction tx = persistence.createTransaction()
      try {
          EntityManager em = persistence.getEntityManager()
          Group group = em.getReference(Group.class, UUID.fromString('0fa2b1a5-1d68-4d69-9fbd-dff348347f93'))
          for (i in (1..250)) {
              User user = new User()
              user.setGroup(group)
              user.setLogin("user_${i.toString().padLeft(3, '0')}")
              user.setName(user.login)
              user.setPassword(passwordEncryption.getPasswordHash(user.id, '1'));
              em.persist(user)
          }
          tx.commit()
      } finally {
          tx.end()
      }
4.2.5.2.6. ServerInfoMBean

ServerInfoMBean provides the general information about this Middleware block: the build number, build date and the server id.

JMX ObjectName: app-core.cuba:type=ServerInfo

4.2.6. Infrastructure Interfaces

Infrastructure interfaces provide access to frequently used functionality of the platform. Most of them are located in the global module and can be used both on the middle and client tiers. However, some of them (Persistence, for example) are accessible only for Middleware code.

Infrastructure interfaces are implemented by Spring Framework beans, so they can be injected into any other managed components (managed beans, Middleware services, generic user interface screen controllers).

Also, like any other beans, infrastructure interfaces can be obtained using static methods of AppBeans class, and can be used in non-managed components (POJO, helper classes etc.).

4.2.6.1. Configuration

The interface helps to obtain references to configuration interfaces.

Examples:

// field injection

@Inject
protected Configuration configuration;
...
String tempDir = configuration.getConfig(GlobalConfig.class).getTempDir();
// setter injection

protected GlobalConfig globalConfig;

@Inject
public void setConfiguration(Configuration configuration) {
    this.globalConfig = configuration.getConfig(GlobalConfig.class);
}
// location

String tempDir = AppBeans.get(Configuration.class).getConfig(GlobalConfig.class).getTempDir();
4.2.6.2. Messages

Messages interface provides methods to get localized message strings.

Let us consider interface methods in detail.

  • getMessage() – returns the localized message by key, pack name and required locale. There are several modifications of this method with different sets of parameters. If locale is not specified in the method parameter, the current user locale is used.

    Examples:

    @Inject
    protected Messages messages;
    ...
    String message1 = messages.getMessage(getClass(), "someMessage");
    String message2 = messages.getMessage("com.abc.sales.web.customer", "someMessage");
    String message3 = messages.getMessage(RoleType.STANDARD);
  • formatMessage() – retrieves a localized message by key, pack name and required locale, then uses it to format the input parameters. The format is defined according to String.format() method rules. There are several modifications of this method with different sets of parameters. If locale is not specified in the method parameter, the current user locale is used.

    Example:

    String formattedValue = messages.formatMessage(getClass(), "someFormat", someValue);
  • getMainMessage() – returns the localized message from the main message pack of the application block.

    Example:

    protected Messages messages = AppBeans.get(Messages.class);
    ...
    messages.getMainMessage("actions.Ok");
  • getMainMessagePack() – returns the name of the main message pack of the application block.

    Example:

    String formattedValue = messages.formatMessage(messages.getMainMessagePack(), "someFormat", someValue);
  • getTools() – returns MessageTools interface instance (see below).

4.2.6.2.1. MessageTools

MessageTools interface is a managed bean containing additional methods for working with localized messages. You can access MessageTools interface either using Messages.getTools() method, or as any other bean – by means of injection or through AppBeans class.

MessageTools methods:

  • loadString() – returns a localized message, specified by reference in msg://{messagePack}/{key} format

    Reference components:

    • msg:// – mandatory prefix.

    • {messagePack} – optional name of the message pack. If it is not specified, it is assumed that the pack name is passed to loadString() as a separate parameter.

    • {key} – message key in the pack.

    Examples of the message references:

    msg://someMessage
    msg://com.abc.sales.web.customer/someMessage
  • getEntityCaption() – returns the localized entity name.

  • getPropertyCaption() – returns the localized name of an entity attribute.

  • hasPropertyCaption() – checks whether the entity attribute was given a localized name.

  • getLocValue() – returns the localized value of the entity attribute based on @LocalizedValue annotation.

  • getMessageRef() – forms a message reference for meta-property which can be used to retrieve the localized name of the entity attribute.

  • getDefaultLocale() – returns default application locale, which is the first one listed in cuba.availableLocales application property.

  • useLocaleLanguageOnly() – returns true, if for all locales supported by the application (defined in cuba.availableLocales property) only the language parameter is specified, without country and variant. This method is used by platform mechanisms which need to find the most appropriate supported locale when locale info is received from the external sources such as operation system or HTTP request.

  • trimLocale() – deletes from the passed locale everything except language, if useLocaleLanguageOnly() method returns true.

You can override MessageTools to extend the set of its methods in your application. Below are the examples of working with the extended interface:

MyMessageTools tools = messages.getTools();
tools.foo();
((MyMessageTools) messages.getTools()).foo();
4.2.6.3. Metadata

Metadata interface provides access to metadata session and view repository.

Interface methods:

  • getSession() – returns the metadata session instance.

  • getViewRepository() – returns the view repository instance.

  • getExtendedEntities() – returns ExtendedEntities instance, intended for working with the extended entities. See more in Extending an Entity.

  • create() – creates an entity instance, taking into account potential extension. See more in Extending an Entity.

  • getTools() – returns MetadataTools interface instance (see below).

4.2.6.3.1. MetadataTools

MetadataTools is a managed bean, containing additional methods for working with metadata. You can access MetadataTools interface by either using Metadata.getTools() method, or as any other bean – by means of injection or through AppBeans class.

`MetadataTools `methods:

  • getAllPersistentMetaClasses() – returns the collection of persistent entities meta-classes.

  • getAllEmbeddableMetaClasses() – returns the collection of embeddable entities meta-classes.

  • getAllEnums() – returns the collection of enumeration classes used as entity attributes types.

  • format() – formats the passed value according to data type of the given meta-property.

  • isSystem() – checks if a meta-property is system, i.e. specified in one of the basic entity interfaces.

  • isPersistent() – checks if a meta-property is persistent, i.e. stored in the database.

  • isTransient() – checks if a meta-property or an arbitrary attribute is non-persistent.

  • isEmbedded() – checks if a meta-property is an embedded object.

  • isAnnotationPresent() – checks if an annotation is present on the class or on one of its ancestors.

  • getNamePatternProperties() – returns collection of meta-properties of attributes included in the instance name, returned by Instance.getInstanceName() method. See @NamePattern.

You can override MetadataTools bean in your application to extend the set of its methods. The examples of working with the extended interface:

MyMetadataTools tools = metadata.getTools();
tools.foo();
((MyMetadataTools) metadata.getTools()).foo();
4.2.6.4. Resources

Resources interface maintains resources loading according to the following rules:

  1. If the provided location is a URL, the resource is downloaded from this URL;

  2. If the provided location begins with classpath: prefix, the resource is downloaded from classpath;

  3. If the location is not a URL and it does not begin with classpath:, then:

    1. The file is searched in the configuration folder of application using the provided location as relative pathname. If the file is found, the resource is downloaded from it;

    2. If the resource is not found at the previous steps, it is downloaded from classpath.

In practice, explicit identification of URL or classpath: prefix is rarely used, so resources are usually downloaded either from the configuration folder or from classpath. The resource in the configuration folder overrides the classpath resource with the same name.

Resources methods:

  • getResourceAsStream() – returns InputStream for the provided resource, or null, if the resource is not found. The stream should be closed after it had been used, for example:

    @Inject
    protected Resources resources;
    ...
    InputStream stream = null;
    try {
        stream = resources.getResourceAsStream(resourceLocation);
        ...
    } finally {
        IOUtils.closeQuietly(stream);
    }

    You can also use "try with resources":

    try (InputStream stream = resources.getResourceAsStream(resourceLocation)) {
        ...
    }
  • getResourceAsString() – returns the indicated resource content as string, or null, if the resource is not found.

4.2.6.5. Scripting

Scripting interface is used to compile and load Java and Groovy classes dynamically (i.e. at runtime) as well as to execute Groovy scripts and expressions.

Scripting methods:

  • evaluateGroovy() – executes the Groovy expression and returns its result.

    cuba.groovyEvaluatorImport application property is used to define the common set of the imported classes inserted into each executed expression. By default, all standard application blocks import PersistenceHelper class.

    The compiled expressions are cached, and this considerably speeds up repeated execution.

    Example:

    @Inject
    protected Scripting scripting;
    ...
    Integer intResult = scripting.evaluateGroovy("2 + 2", new Binding());
    
    Binding binding = new Binding();
    binding.setVariable("instance", new User());
    Boolean boolResult = scripting.evaluateGroovy("return PersistenceHelper.isNew(instance)", binding);
  • runGroovyScript() – executes Groovy script and returns its result.

    The script should be located either in application configuration folder or in classpath (the current Scripting implementation supports classpath resources within JAR files only). A script in the configuration folder overrides the script in classpath with the same name.

    The path to the script is constructed using separators /. The separator is not required in the beginning of the path.

    Example:

    @Inject
    protected Scripting scripting;
    ...
    Binding binding = new Binding();
    binding.setVariable("itemId", itemId);
    BigDecimal amount = scripting.runGroovyScript("com/abc/sales/CalculatePrice.groovy", binding);
  • loadClass() – loads Java or Groovy class using the following steps:

    1. If the class is already loaded, it will be returned.

    2. The Groovy source code (file *.groovy) is searched in the configuration folder. If it is found, it will be compiled and the class will be returned.

    3. The Java source code (file *.java) is searched in the configuration folder. If it is found, it will be compiled and the class will be returned.

    4. The compiled class is searched in classpath. If it is found, it will be loaded and returned.

    5. If nothing is found, null will be returned.

    The files in configuration folder containing Java and Groovy source code can be modified at runtime. On the next loadClass() call the corresponding class will be recompiled and the new one will be returned, with the following restrictions:

    • The type of the source code must not be changed from Groovy to Java;

    • If Groovy source code was once compiled, the deletion of the source code file will not lead to loading of another class from classpath. Instead of this, the class compiled from the removed source code will still be returned.

    Example:

    @Inject
    protected Scripting scripting;
    ...
    Class calculatorClass = scripting.loadClass("com.abc.sales.PriceCalculator");
  • getClassLoader() – returns ClassLoader, which is able to work according to the rules for loadClass() method described above.

Cache of the compiled classes can be cleaned at runtime using CachingFacadeMBean JMX bean.

4.2.6.6. Security

This interface provides authorization – checking user access rights to different objects in the system. Most of the interface methods delegate to the corresponding methods of current UserSession object, but before this they search for an original meta-class of the entity, which is important for projects with extensions. Besides methods duplicating UserSession functionality, this interface contains isEntityAttrReadPermitted() and isEntityAttrUpdatePermitted() methods that check attribute path availability with respect to availability of all attributes and entities included in the path.

The Security interface is recommended to use everywhere instead of direct calling of the UserSession.isXYXPermitted() methods.

See more in User Authentication.

4.2.6.7. TimeSource

TimeSource interface provides the current time. Using new Date() and similar methods in the application code is not recommended.

Examples:

@Inject
protected TimeSource timeSource;
...
Date date = timeSource.currentTimestamp();
long startTime = AppBeans.get(TimeSource.class).currentTimeMillis();
4.2.6.8. UserSessionSource

The interface is used to obtain current user session object. See more in User Authentication.

4.2.6.9. UuidSource

The interface is used to obtain UUID values, including those used for entity identifiers. Using UUID.randomUUID() in the application code is not recommended.

To call from a static context, you can use the UuidProvider class, which also has an additional fromString() method that works faster than the standard UUID.fromString() method.

4.2.6.10. DataManager

DataManager interface provides CRUD functionality on both middle and client tiers. It is a universal tool for loading entity graphs from the database and saving changes applied to detached entity instances.

DataManager always starts a new transaction and commits it on operation completion, thus returning entities in the detached state.

DataManager always applies security restrictions when invoked from a client, and ignores them by default when invoked from middleware code. If you want to check security when using DataManager in your middleware code, obtain a wrapper via DataManager.secure() method and call its methods. Alternatively, you can set the cuba.dataManagerChecksSecurityOnMiddleware application property to turn on security check for the whole application.

DataManager methods are listed below:

  • load(), loadList() – loads a graph of entities according to the parameters of the LoadContext object passed to it.

    LoadContext must include either a JPQL query or an entity identifier. If both are defined, the query is used, and the identifier is ignored. The rules for queries creation are similar to those described in Executing JPQL Queries . The difference is that the query in LoadContext may only use named parameters; positional parameters are not supported.

    load() and loadList() methods check user permission EntityOp.READ for the entity being loaded. Additionally, loading entities from DB is subject for access group constraints.

    Examples of loading entities in the screen controller:

    @Inject
    private DataManager dataManager;
    
    private Book loadBookById(UUID bookId) {
        LoadContext loadContext = LoadContext.create(Book.class)
                .setId(bookId).setView("book.edit");
        return dataManager.load(loadContext);
    }
    
    private List<BookPublication> loadBookPublications(UUID bookId) {
        LoadContext loadContext = LoadContext.create(BookPublication.class)
                .setQuery(LoadContext.createQuery("select p from library$BookPublication p where p.book.id = :bookId")
                    .setParameter("bookId", bookId))
                .setView("bookPublication.full");
        return dataManager.loadList(loadContext);
    }
  • commit() – saves the set of entities passed in CommitContext to the database. Collections of entities for updating and deletion must be specified separately.

    The method returns the set of entity instances returned by EntityManager.merge(); essentially these are fresh instances just updated in DB. Further work should be performed with these returned instances to prevent data loss or optimistic locking. You can ensure that required attributes are present in the returned entities by setting a view for each saved instance using CommitContext.getViews() map.

    This method checks user permissions EntityOp.UPDATE for the updated entities and EntityOp.DELETE for the deleted ones.

    Examples for saving a collection of entities:

    @Inject
    private DataManager dataManager;
    
    private void saveBookInstances(List<BookInstance> toSave, List<BookInstance> toDelete) {
        CommitContext commitContext = new CommitContext(toSave, toDelete);
        dataManager.commit(commitContext);
    }
    
    private Set<Entity> saveAndReturnBookInstances(List<BookInstance> toSave, View view) {
        CommitContext commitContext = new CommitContext();
        for (BookInstance bookInstance : toSave) {
            commitContext.addInstanceToCommit(bookInstance, view);
        }
        return dataManager.commit(commitContext);
    }
  • reload() - convenience methods to reload a specified instance from the database with the required view. They delegate to load() method.

  • remove() - removes a specified instance from the database. Delegates to commit() method.

While loading data, DataManager may also implement additional functionality described below.

Tip

See DataManager vs. EntityManager for information on differences between DataManager and EntityManager.

4.2.6.10.1. Queries with distinct

If a screen contains a table with paging, and JPQL that is used to load data can be modified at run time as a result of applying a generic filter or access group constraints, the following can happen when distinct operator is omitted in JPQL queries:

  • If a collection is joined at the database level, the loaded dataset will contain duplicate rows.

  • On client level, the duplicates disappear in the datasource as they are added to a map (java.util.Map).

  • In case of paged table, a page may show fewer lines than requested, while the total number of lines exceeds requested.

Thus, we recommend including distinct in JPQL queries, which ensures the absence of duplicates in the dataset returned from the database. However, certain DB servers (PostgreSQL in particular) have performance problems when executing SQL queries with distinct if the number of returned records is large (more than 10000).

To solve this, the platform contains a mechanism to operate correctly without distinct at SQL level. This mechanism is enabled by cuba.inMemoryDistinct application property. When activated, it does the following:

  • The JPQL query should still include select distinct.

  • DataManager cuts distinct out of the JPQL query before sending it to ORM.

  • After the data page is loaded by DataManager, it deletes the duplicates and runs additional queries to DB in order to retrieve the necessary number of rows which are then returned to the client.

4.2.6.10.2. Sequential Queries

DataManager can select data from the results of previous requests. This capability is used by the generic filter for sequential application of filters.

The mechanism works as follows:

  • If a LoadContext with defined attributes prevQueries and queryKey is provided, DataManager executes the previous query and saves identifiers of retrieved entities in the SYS_QUERY_RESULT table (corresponding to sys$QueryResult entity), separating the sets of records by user sessions and the query session key queryKey.

  • The current query is modified to be combined with the results of the previous one, so that the resulting data complies with the conditions of both queries combined by AND.

  • The process may be further repeated. In this case the gradually reduced set of previous results is deleted from the SYS_QUERY_RESULT table and refilled again.

The SYS_QUERY_RESULT table should be periodically cleaned of all unnecessary query results left by terminated user sessions. This is done by the deleteForInactiveSessions() method of the QueryResultsManagerAPI bean. In an application with enabled cuba.allowQueryFromSelected property this method should be called by scheduled tasks, for example:

<task:scheduled-tasks scheduler="scheduler">
    <task:scheduled ref="cuba_QueryResultsManager" method="deleteForInactiveSessions" fixed-rate="600000"/>
</task:scheduled-tasks>

4.2.7. PersistenceHelper

A helper class for obtaining the information on persistent entities. Unlike the Persistence and PersistenceTools beans, this class is available on all tiers.

The PersistenceHelper bean has the following methods:

  • isLoaded() - determines if an attribute is loaded from the database. The attribute is loaded if it is included into a view, or if it is a local attribute and a view was not provided to the loading mechanism (EntityManager or DataManager). Only immediate attributes of the entity are supported.

  • isNew() – determines if the passed instance is newly created, i.e., in the New state. Also returns true if this instance is actually in Managed state but newly-persisted in the current transaction, or if it is not a persistent entity.

  • isManaged() - determines if the passed instance is Managed, i.e. attached to a persistence context.

  • isDetached() – determines if the passed instance is in the Detached state. Also returns true, if this instance is not a persistent entity.

  • isSoftDeleted() – determines if the passed entity class supports the soft deletion.

  • getEntityName() – returns the name of the entity specified in the @Entity annotation.

4.2.8. AppContext

AppContext is a system class, which stores references to certain common components for each application block in its static fields:

  • ApplicationContext of Spring Framework.

  • Set of application properties loaded from app.properties files.

  • ThreadLocal variable, storing SecurityContext instances.

  • Collection of application lifecycle listeners (AppContext.Listener).

When the application is started, AppContext is initialized using loader classes, specific for each application block:

  • Middleware loader – AppContextLoader

  • Web Client loader – WebAppContextLoader

  • Web Portal loader – PortalAppContextLoader

  • Desktop Client loader – DesktopAppContextLoader

AppContext can be used in the application code for the following tasks:

  • Registering listeners, triggered after full initialization and before termination of the application, for example:

    AppContext.addListener(new AppContext.Listener() {
        @Override
        public void applicationStarted() {
            System.out.println("Application is ready");
        }
    
        @Override
        public void applicationStopped() {
            System.out.println("Application is closing");
        }
    });

    At the moment of applicationStarted() call:

    • All the beans are fully initialized and their @PostConstruct methods are executed.

    • Static AppBeans.get() methods can be used for obtaining beans.

    • The AppContext.isStarted() method returns true.

    • The AppContext.isReady() method returns false.

    • If cuba.automaticDatabaseUpdate application property is enabled, all database update scripts are successfully executed (in the Middleware block).

    At the moment of applicationStopped() call:

    • All the beans are operational and can be obtained via AppBeans.get() methods.

    • AppContext.isStarted() method returns false.

    • The AppContext.isReady() method returns false.

    A real example of using AppContext.Listener can be found in Running Code at Application Start.

  • Getting the application property values, stored in app.properties files in case they are not available through configuration interfaces.

  • Passing SecurityContext to new execution threads, see User Authentication.

4.2.9. Application Properties

Application properties represent named values of different types, which determine various aspects of application configuration and functionality. The platform uses application properties extensively, and you can also employ them to configure application-specific features.

Platform application properties can be classified by intended purpose as follows:

  • Configuration parameters – specify sets of configuration files and certain user interface parameters, i.e. determine the application functionality. Values of configuration parameters are usually defined for the application project at development time.

  • Deployment parameters – describe various URLs to connect application blocks, DBMS type, security settings etc. Values of deployment parameters are usually depend on the environment where the application instance is installed.

  • Runtime parameters – audit settings, email sending parameters etc. Values of these properties can be changed when needed at the application run time even without restart.

Values of application properties can be set in the database, in the property files, or via Java system properties. Besides, a value set in a file overrides the value with the same name from the database. A value set as a Java system property overrides both values from files and from the database.

Some properties do not support setting values in the database for the following reason: their values are needed when the database is not accessible to the application code yet. These are configuration and deployment parameters mentioned above. So you can only define them in property files or via Java system properties. Runtime parameters can always be set in the database (and possibly be overridden by values in files or system properties).

Typically, an application property is used in one or several application blocks. For example, cuba.persistenceConfig is used only in Middleware, cuba.web.useLightHeader is used in Web Client, while cuba.springContextConfig is used in all blocks. It means that if you need to set some value to a property, you should do it in all blocks that use this property. Properties stored in the database are automatically available to all blocks, so you set them just in one place (in the database table) regardless of what blocks use them. Moreover, there is a standard UI screen to manage properties of this type: see Administration > Application Properties. Properties stored in files should be set separately in the respective files of the blocks.

Tip

When you need to set a value to a platform property, find this property in the documentation. If the documentation states that the property is stored in the database, use the Administration > Application Properties screen to set its value. Otherwise, find out what blocks use the property and define it in the app.properties files of this blocks. For example, if the documentation states that the property is used in all blocks, and your application consists of Middleware and Web Client, you should define the property in the app.properties file of the core module and in the web-app.properties file of the web module. Deployment parameters can also be set outside of project files in the configuration directory. See Storing Properties in Files for details.

You can access application properties in your code using the following mechanisms:

  • Configuration interfaces. If you define application properties as annotated methods of a configuration interface, the application code will have typed access to the properties. Configuration interfaces allow you to define and access properties of all types of storage: database, files and system properties.

  • The getProperty() method of the AppContext class. If you set a property in a file or as a Java system property, you can read its value using this method. This approach has the following drawbacks:

    • Properties stored in the database are not supported.

    • Unlike invoking an interface method, you have to provide the property name as String.

    • Unlike getting a result of a specific type, you can only get the property value as String.

4.2.9.1. Storing Properties in Files

Properties that determine configuration and deployment parameters are specified in special property files named according to the *app.properties pattern. Each application block contains a set of such files, including files from the platform base projects. The set of property files is defined as follows:

  • For web application blocks (Middleware, Web Client, Web Portal) the set of property files is specified in the appPropertiesConfig parameter of web.xml.

  • For the Desktop Client block the standard way to specify the set of property files is to override the getDefaultAppPropertiesConfig() method in an application class inherited from com.haulmont.cuba.desktop.App.

For example, the set of property files of the Middleware block is specified in the web/WEB-INF/web.xml file of the core module and looks as follows:

<context-param>
    <param-name>appPropertiesConfig</param-name>
    <param-value>
        classpath:cuba-app.properties
        classpath:app.properties
        file:${catalina.home}/conf/app-core/local.app.properties
    </param-value>
</context-param>

The classpath: prefix means that the corresponding file can be found in the Java classpath, while file: prefix means that it should be loaded from the file system. Java system properties can be used: in this example, catalina.home is the Tomcat installation path.

An order in which files are declared is important because the values, specified in each subsequent file override the values of the properties with the same name, specified in the preceding files. This allows you to override platform properties in the application.

The last file in the above set is local.app.properties. It can be used to override application properties upon deployment. If the file does not exist, it is silently ignored. You can create this file on the application server and define all properties specific to the environment in it. As a result, the settings will be separated from the application, and you will be able to update the application without fear of losing the specific configuration information. The Using Tomcat in Production section contains an example of using the local.app.properties file.

For Desktop Client, JVM command line arguments serve as an equivalent of local.app.properties. In this block, the properties loader treats all the arguments containing "=" sign as a key/value pair and uses them to replace corresponding application properties specified in app.properties files.

Tip

Use the following rules when create *.properties files:

  • File encoding – UTF-8.

  • The key can contain Latin letters, numbers, periods and underscores.

  • The value is entered after (=) sign.

  • Do not quote values using " or ' brackets.

  • Set file paths either in UNIX (/opt/haulmont/) or Windows (c:\\haulmont\\) format.

  • You can use \n \t \r codes. The \ sign is a reserved code, use \\ to insert it in a value. See more at: http://docs.oracle.com/javase/tutorial/java/data/characters.html.

  • To enter a multi-line value, use \ sign at the end of each line .

4.2.9.2. Storing Properties in the Database

Application properties that represent runtime parameters are stored in the SYS_CONFIG database table.

Such properties have the following distinctive features:

  • As the property value is stored in the database, it is defined in a single location, regardless of what application blocks use it.

  • The value can be changed and saved at runtime in the following ways:

    • Using the Administration > Application Properties screen.

    • Using the ConfigStorageMBean JMX bean.

    • If the configuration interface has a setter method, you can set the property value in the application code.

  • Property value can be overridden for a particular application block in its *app.properties file or via Java system property with the same name.

It is important to mention, that access to properties stored in the database on the client side leads to Middleware requests. This is less efficient than retrieving properties from local *app.properties files. To reduce the number of requests, the client caches properties for the lifetime of configuration interface implementation instance. Thus, if you need to access the properties of a configuration interface from some UI screen for several times, it is recommended to get the reference to this interface upon screen initialization and save it to a screen controller field for further access.

4.2.9.3. Configuration Interfaces

The configuration interfaces mechanism enables working with application properties using Java interface methods, providing the following benefits:

  • Typed access – application code works with actual data types (String, Boolean, Integer etc.).

  • Instead of string property identifiers, the application code uses interface methods, which are checked by the compiler and you can use code completion when working in an IDE.

Example of reading the transaction timeout value in the Middleware block:

@Inject
private ServerConfig serverConfig;

public void doSomething() {
    int timeout = serverConfig.getDefaultQueryTimeoutSec();
    ...
}

If injection is impossible, the configuration interface reference can be obtained via the Configuration infrastructure interface:

int timeout = AppBeans.get(Configuration.class)
        .getConfig(ServerConfig.class)
        .getDefaultQueryTimeoutSec();
Warning

Configuration interfaces are not regular Spring managed beans. They can only be obtained through explicit interface injection or via Configuration.getConfig() but not through AppBeans.get().

4.2.9.3.1. Using Configuration Interfaces

To create a configuration interface in your application, do the following:

  • Create an interface inherited from com.haulmont.cuba.core.config.Config (not to be confused with the entity class com.haulmont.cuba.core.entity.Config).

  • Add @Source annotation to specify where the property values should be stored:

    • SourceType.SYSTEM – values will be taken from the system properties of the given JVM using the System.getProperty() method.

    • SourceType.APP – values will be taken from *app.properties files.

    • SourceType.DATABASE – values will be taken from the database.

  • Create property access methods (getters / setters). If you are not going to change the property value from the application code, do not create setter. A getter return type defines the property type. Possible property types are described below.

  • Add @Property annotation defining the property name to the getter.

  • You can optionally set @Source annotation for a particular property if its source differs from the interface source.

Example:

@Source(type = SourceType.DATABASE)
public interface SalesConfig extends Config {

    @Property("sales.companyName")
    String getCompanyName();
}

Do not create any implementation classes because the platform will create a required proxy automatically when you inject the configuration interface or obtain it through Configuration.

4.2.9.3.2. Property Types

The following property types are supported in the platform out-of-the-box:

  • String, primitive types and their object wrappers (boolean, Boolean, int, Integer, etc.)

  • enum. The property value is stored in a file or in the database as the value name of the enumeration.

    If the enum implements the EnumClass interface and has the static fromId() method for getting a value by an identifier, you can specify that the enum identifier should be stored instead of value with the @EnumStore annotation. For example:

    @Property("myapp.defaultCustomerGrade")
    @DefaultInteger(10)
    @EnumStore(EnumStoreMode.ID)
    CustomerGrade getDefaultCustomerGrade();
    
    @EnumStore(EnumStoreMode.ID)
    void setDefaultCustomerGrade(CustomerGrade grade);
  • Persistent entity classes. When accessing a property of the entity type, the instance defined by the property value is loaded from the database.

To support arbitrary types, use TypeStringify and TypeFactory classes to convert the value to/from a string and specify these classes for the property with @Stringify and @Factory annotations.

Let us consider this process using the UUID type as an example.

  • Create class com.haulmont.cuba.core.config.type.UuidTypeFactory inherited from com.haulmont.cuba.core.config.type.TypeFactory and implement the following method in it:

    public Object build(String string) {
        if (string == null) {
            return null;
        }
        return UUID.fromString(string);
    }
  • There is no need to create TypeStringify as toString() method is sufficient in this case.

  • Annotate the property in the configuration interface:

    @Factory(factory = UuidTypeFactory.class)
    UUID getUuidProp();
    void setUuidProp(UUID value);

The platform provides TypeFactory implementations for the following types:

  • UUIDUuidTypeFactory, as described above.

  • java.util.DateDateFactory. Date value must be specified in yyyy-MM-dd HH:mm:ss.SSS format, for example:

    cuba.test.dateProp = 2013-12-12 00:00:00.000
  • List<Integer> (the list of integers) – IntegerListTypeFactory. The property value must be specified in the form of numbers, separated by spaces, for example:

    cuba.test.integerListProp = 1 2 3
  • List<String> (the list of strings) – StringListTypeFactory. The property value must be specified as a list of strings separated by "|" sign, for example:

    cuba.test.stringListProp = aaa|bbb|ccc
4.2.9.3.3. Default Values

You can specify default values for properties defined by configuration interfaces. These values will be returned instead of null if the property is not set in the storage location – the database or *app.properties files.

A default value can be specified as a string using the @Default annotation, or as a specific type using other annotations from com.haulmont.cuba.core.config.defaults package:

@Property("cuba.email.adminAddress")
@Default("address@company.com")
String getAdminAddress();

@Property("cuba.email.delayCallCount")
@Default("2")
int getDelayCallCount();

@Property("cuba.email.defaultSendingAttemptsCount")
@DefaultInt(10)
int getDefaultSendingAttemptsCount();

@Property("cuba.test.dateProp")
@Default("2013-12-12 00:00:00.000")
@Factory(factory = DateFactory.class)
Date getDateProp();

@Property("cuba.test.integerList")
@Default("1 2 3")
@Factory(factory = IntegerListTypeFactory.class)
List<Integer> getIntegerList();

@Property("cuba.test.stringList")
@Default("aaa|bbb|ccc")
@Factory(factory = StringListTypeFactory.class)
List<String> getStringList();

A default value for an entity is a string of the {entity_name}-{id}-{optional_view_name} format, for example:

@Default("sec$User-98e5e66c-3ac9-11e2-94c1-3860770d7eaf-browse")
User getAdminUser();

@Default("sec$Role-a294aef0-3ac9-11e2-9433-3860770d7eaf")
Role getAdminRole();

4.2.10. Messages Localization

Applications based on CUBA platform support messages localization, which means that all user interface elements can be displayed in the language, selected by user.

Language selection options are determined by the combination of cuba.localeSelectVisible and cuba.availableLocales application properties.

This section describes the localization mechanism and rules of localized messages creation. For information about obtaining messages see Getting Localized Messages.

4.2.10.1. Message Packs

A message pack is a set of properties files with the names in format of messages{_XX}.properties located in a single Java package. XX suffix indicates the language of the messages in this file and corresponds to the language code in Locale.getLanguage(). It is also possible to use other Locale attributes, for example, country. In this case the pack file will look like messages{_XX_YY}.properties. One of the files in the pack can have no language suffix – it is the default file. The name of the message pack corresponds to the name of the Java package, which contains the pack files.

Let us consider the following example:

/com/abc/sales/gui/customer/messages.properties
/com/abc/sales/gui/customer/messages_fr.properties
/com/abc/sales/gui/customer/messages_ru.properties

This pack consists of 3 files – one for Russian, one for French and a default file. The name of the pack is com.abc.sales.gui.customer.

Message files contain key/value pairs, where the key is the message identifier referenced by the application code, and the value is the message itself in the language of the file. The rules for matching pairs are similar to those of java.util.Properties properties files with the following specifics:

  • File encoding – UTF-8 only.

  • Including other message packs is supported using @include key. Several packs can be included using comma-separated enumeration. In this case, if some message key is found in both the current and the included pack, the message from the current pack will be used. Example of including packs:

    @include=com.haulmont.cuba.web, com.abc.sales.web
    
    someMessage=Some Message
    ...

Messages are retrieved from the packs using Messages interface methods according to the following rules:

  • At first step search is performed in the application configuration directory.

    • messages_XX.properties file is searched in the directory specified by the message pack name, where XX is the code of the required language.

    • If there is no such file, default messages.properties file is searched in the same directory.

    • If either the required language file or the default file is found, it is loaded together with all @include files, and the key message is searched in it.

    • If the file is not found or it does not contain the proper key, the directory is changed to the parent one and the search procedure is repeated. The search continues until the root of the configuration directory is reached.

  • If the message is not found in the configuration directory, the search is performed in classpath according to the same algorithm.

  • On client tier, if the message is not found after the previous steps, the query is made to Middleware, and the message is searched there in the similar way.

  • If the message is found, it is cached and returned. If not, the fact that the message is not present is cached as well and the key which was passed for search is returned. Thus, the complex search procedure is only performed once and further on the result is loaded from the local cache of the application block.

Tip

It is recommended to organize message packs as follows:

  • If the application is not intended for internationalization, you can include message strings directly into the application code instead of using packs or use messages.properties default files to separate resources from code.

  • If the application is international, it is reasonable to use default files for the language of the application primary audience or for the English language, so that the messages from these default files are displayed to the user if the messages in the required language are not found.

4.2.10.2. Main Message Pack

Each standard application block should have its own main message pack. For the client tier blocks the main message pack contains main menu entries and common UI elements names (for example, names of OK and Cancel buttons). The main pack also determines Datatype transformation formats for all application blocks, including Middleware.

cuba.mainMessagePack application property is used to specify the main message pack. The property value can be either a single pack or list of packs separated by spaces. For example:

cuba.mainMessagePack=com.haulmont.cuba.web com.abc.sales.web

In this case the messages in the second pack of the list will override those from the first pack. Thus, the messages defined in the base projects packs can be overridden in the application project.

4.2.10.3. Entity and Attributes Names Localization

To display localized names of the entities and attributes in UI, create special message packs in the Java packages containing the entities. Use the following format in message files:

  • Key of the entity name – simple class name (without package).

  • Key of the attribute name – simple class name, then the name of the attribute separated by period.

The example of default English localization of com.abc.sales.entity.Customer entity – /com/abc/sales/entity/messages.properties file:

Customer=Customer
Customer.name=Name
Customer.email=Email

Order=Order
Order.customer=Customer
Order.date=Date
Order.amount=Amount

Such message packs are usually used implicitly by the framework, for example, by Table and FieldGroup visual components. Besides, you can obtain the names of the entities and attributes using the following methods:

  • Programmatically – by MessageTools getEntityCaption(), getPropertyCaption() methods;

  • In XML screen descriptor – by reference to the message according to MessageTools.loadString() rules: msg://{entity_package}/{key}, for example:

    caption="msg://com.abc.sales.entity/Customer.name"
4.2.10.4. Enum Localization

To localize the enumeration names and values, add messages with the following keys to the message pack located in the Java package of the enumeration class:

  • Enumeration name key – simple class name (without package);

  • Value key – simple class name, then the value name separated by period.

For example, for enum

package com.abc.sales;

public enum CustomerGrade {
    PREMIUM,
    HIGH,
    STANDARD
}

default English localization file /com/abc/sales/messages.properties should contain the following lines:

CustomerGrade=Customer Grade
CustomerGrade.PREMIUM=Premium
CustomerGrade.HIGH=High
CustomerGrade.STANDARD=Standard

Localized enum values are automatically used by different visual components such as LookupField. You can obtain localized enum value programmatically: use getMessage() method of the Messages interface and simply pass the enum instance to it.

4.2.11. User Authentication

This section describes some access control aspects from the developer’s point of view. For complete information on configuring user data access restrictions, see Security Subsystem.

4.2.11.1. UserSession

User session is the main element of access control mechanism of CUBA applications. It is represented by the UserSession object, which is associated with the currently authenticated user and contains information about user rights. The UserSession object can be obtained in any application block using the UserSessionSource infrastructure interface.

The UserSession object is created on Middleware during LoginService.login() method execution after the user is authenticated using a name and a password. The object is then cached in the Middleware block and returned to the client tier. When running in cluster, the session object is replicated to all cluster members. The client tier also stores the session object after receiving it, associating it with the active user in one way or another (for example, storing it in HTTP session). Further on, all Middleware invocations on behalf of this user are accompanied by passing the session identifier (of UUID type). This process does not need any special support in the application code, as the session identifier is passed automatically, regardless of the signature of invoked methods. Processing of client invocations in the Middleware starts from retrieving session from the cache using the obtained identifier. Then the session is associated with the request execution thread. The session object is deleted from the cache when the LoginService.logout() method is called or when the timeout defined by cuba.userSessionExpirationTimeoutSec application property expires.

Thus the session identifier created when the user logs into the system is used for user authentication during each Middleware invocation.

The UserSession object also contains methods for current user authorization – validation of the rights to system objects: isScreenPermitted(), isEntityOpPermitted(), isEntityAttrPermitted(), isSpecificPermitted(). However, it is recommended to use the Security infrastructure interface for programmatic authorization.

The UserSession object can contain named attributes of arbitrary serializable type. The attributes are set by setAttribute() method and returned by getAttribute() method. The latter is also able to return the following session parameters, as if they were attributes:

  • userId – ID of the currently registered or substituted user;

  • userLogin – login of the currently registered or substituted user in lowercase.

The session attributes are replicated in the Middleware cluster, same as the other user session data.

4.2.11.2. Login

Standard user login process:

  • The user enters his username and password.

  • Application client block hashes the password using getPlainHash() method of PasswordEncryption bean and invokes LoginService.login() Middleware method passing the user login and password hash to it.

  • LoginService delegates execution to the LoginWorker bean, which loads User object by the entered login, hashes the obtained password hash again using user identifier as salt and compares the obtained hash to the password hash stored in the DB. In case of mismatch, LoginException is thrown.

  • If the authentication is successful, all the access parameters of the user (roles list, rights, restrictions and session attributes) are loaded to the created UserSession instance.

Password hashing algorithm is implemented by the EncryptionModule type bean and is specified in cuba.passwordEncryptionModule application property. SHA-1 is used by default.

The platform has a mechanism for the protection against password brute force cracking. The protection is enabled by the cuba.bruteForceProtection.enabled application property on Middleware. If the protection is enabled then the combination of user login and IP address is blocked for a time interval in case of multiple unsuccessful login attempts. A maximum number of login attempts for the combination of user login and IP address is defined by the cuba.bruteForceProtection.maxLoginAttemptsNumber application property (default value is 5). Blocking interval in seconds is defined by the cuba.bruteForceProtection.blockIntervalSec application property (default value is 60).

It is possible that the user password (actually, password hash) is not stored in the database, but is verified by external means, for example, by means of integration with LDAP. In this case the authentication is in fact performed by the client block, while the Middleware "trusts" the client by creating the session based on user login only, without the password, using LoginService.loginTrusted() method. This method requires satisfying the following conditions:

Login to the system is also required for scheduled automatic processes as well as for connecting to the Middleware beans using JMX interface. Formally, these actions are considered administrative and they do not require authentication as long as no entities are changed in the database. When an entity is persisted to the database, the process requires login of the user who is making the change so that the login of the user responsible for the changes is stored.

An additional benefit from login to the system for an automatic process or for JMX call is that the server log output is displayed with the current user login if the user session is set to the execution thread. This simplifies searching messages created by specific process during log parsing.

System access for the processes within Middleware is done using LoginWorker.loginSystem() call passing the login (without password) of the user on whose behalf the process will be executed. As result, UserSession object will be created and cached in the corresponding Middleware block but it will not be replicated in the cluster.

See more about processes authentication inside Middleware in System Authentication.

4.2.11.3. SecurityContext

SecurityContext class instance stores information about the user session for the current execution thread. It is created and passed to AppContext.setSecurityContext() method in the following moments:

  • For the Web Client and Web Portal blocks – at the beginning of processing of each HTTP request from the user browser.

  • For the Middleware block – at the beginning of processing of each request from the Client tier.

  • For the Desktop Client block – once after the user login, as the desktop application is running in single user mode.

In the first two cases, SecurityContext is removed from the execution thread when the request execution is finished.

If you create a new execution thread from the application code, pass the current SecurityContext instance to it as in the example below:

final SecurityContext securityContext = AppContext.getSecurityContext();
executor.submit(new Runnable() {
    public void run() {
        AppContext.setSecurityContext(securityContext);
        // business logic here
    }
});

4.2.12. Exceptions Handling

This section describes various aspects of working with exceptions in CUBA applications.

4.2.12.1. Exception Classes

The following rules should be followed when creating your own exception classes:

  • If the exception is part of business logic and requires some non-trivial actions to handle it, the exception class should be made checked (inherited from Exception). Such exceptions are handled by the invoking code.

  • If the exception indicates an error and assumes interruption of execution and a simple action like displaying the error information to the user, its class should be unchecked (inherited from RuntimeException). Such exceptions are processed by special handler classes registered in the client blocks of the application.

  • If the exception is thrown and processed in the same block, its class should be declared in corresponding module. If the exception is thrown on Middleware and processed on the client tier, the exception class should be declared in the global module.

The platform contains a special unchecked exception class SilentException. It can be used to interrupt execution without showing any messages to the user or writing them to the log. SilentException is declared in the global module, and therefore is accessible both in Middleware and client blocks.

4.2.12.2. Passing Middleware Exceptions

If an exception is thrown on Middleware as a result of handling a client request, the execution terminates and the exception object is returned to the client. The object usually includes the chain of underlying exceptions. This chain can contain classes which are inaccessible for the client tier (for example, JDBC driver exceptions). For this reason, instead of sending this chain to the client we send its representation inside a specially created RemoteException object.

The information about the causing exceptions is stored as a list of RemoteException.Cause objects. Each Cause object always contains an exception class name and its message. Moreover, if the exception class is "supported by client", Cause stores the exception object as well. This enables passing information to the client in the exception fields.

Exception class should be annotated by @SupportedByClient if its objects should be passed to the client tier as Java objects. For example:

@SupportedByClient
public class WorkflowException extends RuntimeException {
...

Thus, when an exception is thrown on Middleware and it is not annotated by @SupportedByClient the calling client code will receive RemoteException containing original exception information in a string form. If the source exception is annotated by @SupportedByClient, the caller will receive it directly. This enables handling the exceptions declared by Middleware services in the application code in the traditional way – using try/catch blocks.

Bear in mind that if you need the exception supported by client to be passed on the client as an object, it should not contain any unsupported exceptions in its getCause() chain. Therefore, if you create an exception instance on Middleware and want to pass it to the client, specify cause parameter only if you are sure that it contains the exceptions known to the client.

ServiceInterceptor class is a service interceptor which packs the exception objects before passing them to the client tier. Besides, it performs exceptions logging. All information about the exception including full stack trace is logged by default. If it is not desirable, add @Logging annotation to the exception class and specify the logging level:

  • FULL – full information, including stacktrace (default).

  • BRIEF – exception class name and message only.

  • NONE – no output.

For example:

@SupportedByClient
@Logging(Logging.Type.BRIEF)
public class FinancialTransactionException extends Exception {
...
4.2.12.3. Client-Level Exception Handlers

Unhandled exceptions in Web Client and Desktop Client blocks thrown on the client tier or passed from Middleware, are passed to the special handlers mechanism. This mechanism is implemented in the gui module and available for both blocks.

The handler should be a managed bean implementing the GenericExceptionHandler interface, handle processing in its handle() method and return true, or immediately return false, if this handler is not able to handle the passed exception. This behaviour enables creating a "chain of responsibility" for handlers.

It is recommended to inherit your handlers from the AbstractGenericExceptionHandler base class, which is able to disassemble the exceptions chain (including ones packed inside RemoteException) and handle specific exception types. Exception types supported by this handler are defined by passing strings array to the base constructor from the handler constructor. Each string of the array should contain one full class name of the handled exception, for example:

@Component("cuba_EntityAccessExceptionHandler")
public class EntityAccessExceptionHandler extends AbstractGenericExceptionHandler {

    public EntityAccessExceptionHandler() {
        super(EntityAccessException.class.getName());
    }
...

If the exception class is not accessible on the client side, specify its name with the string literal:

@Component("cuba_OptimisticExceptionHandler")
public class OptimisticExceptionHandler extends AbstractGenericExceptionHandler implements Ordered {

    public OptimisticExceptionHandler() {
        super("javax.persistence.OptimisticLockException");
    }
...

In the case of using AbstractGenericExceptionHandler as a base class, the processing logic is located in doHandle() method and looks as follows:

@Override
protected void doHandle(String className, String message, @Nullable Throwable throwable, WindowManager windowManager) {
    String msg = messages.getMainMessage("zeroBalance.message");
    windowManager.showNotification(msg, IFrame.NotificationType.ERROR);
}

If the name of the exception class is insufficient to make a decision whether this handler can be applied to the exception, canHandle() method should be defined. This method accepts also the text of the exception. If the handler is applicable for this exception, the method must return true. For example:

@Component("cuba_NumericOverflowExceptionHandler")
public class NumericOverflowExceptionHandler extends AbstractGenericExceptionHandler {

    public NumericOverflowExceptionHandler() {
        super(EclipseLinkException.class.getName());
    }

    @Override
    protected boolean canHandle(String className, String message, @Nullable Throwable throwable) {
        return StringUtils.containsIgnoreCase(message, "Numeric field overflow");
    }
...

4.3. Database Components

This section provides information on how to configure the application for working with particular DBMS. It also describes a script-based mechanism, which enables creating a new database and keeping it up-to-date throughout the entire cycle of the development and operation of the application.

Database components belong to the Middleware block; other blocks of the application do not have direct access to the database.

Some additional information on working with the database is provided in the Working with Databases section.

4.3.1. DBMS Types

The type of the DBMS used in the application is defined by the cuba.dbmsType and (optionally) cuba.dbmsVersion application properties. These properties affect various platform mechanisms depending on the database type.

The application connects to the database through the javax.sql.DataSource which is extracted from JNDI by the name specified in the cuba.dataSourceJndiName application property (java:comp/env/jdbc/CubaDS by default). Configuration of the data source for standard deployment is defined in the context.xml file of the core module. The data source should use a proper JDBC driver for the selected DBMS.

The platform supports the following types of DBMS "out of the box":

cuba.dbmsType cuba.dbmsVersion

HSQLDB

hsql

PostgreSQL 8.4+

postgres

Microsoft SQL Server 2005, 2008

mssql

Microsoft SQL Server 2012+

mssql

2012

Oracle Database 11g+

oracle

MySQL 5.6+

mysql

The table below describes the recommended mapping of data types between entity attributes in Java and table columns in different DBMS. CUBA Studio automatically chooses these types when generates scripts to create and update the database. The operation of all platform mechanisms is guaranteed when you use these types.

Java HSQL PostgreSQL MS SQL Server Oracle MySQL

UUID

varchar(36)

uuid

uniqueidentifier

varchar2(32)

varchar(32)

Date

timestamp

timestamp

datetime

timestamp

datetime(3)

java.sql.Date

timestamp

date

datetime

date

date

java.sql.Time

timestamp

time

datetime

timestamp

time(3)

BigDecimal

decimal(p, s)

decimal(p, s)

decimal(p, s)

number(p, s)

decimal(p, s)

Double

double precision

double precision

double precision

float

double precision

Long

bigint

bigint

bigint

number(19)

bigint

Integer

integer

integer

integer

integer

integer

Boolean

boolean

boolean

tinyint

char(1)

boolean

String (limited)

varchar(n)

varchar(n)

varchar(n)

varchar2(n)

varchar(n)

String (unlimited)

longvarchar

text

varchar(max)

clob

longtext

byte[]

longvarbinary

bytea

image

blob

longblob

Usually, the whole work to convert the data between the database and the Java code is performed by the ORM layer in conjunction with the appropriate JDBC driver. This means that no manual conversion is required when working with the data using the EntityManager methods and JPQL queries; you should simply use Java types listed in the left column of the table.

When using native SQL through EntityManager.createNativeQuery() or through QueryRunner, some types in the Java code will be different from those mentioned above, depending on DBMS used. In particular, this applies to attributes of the UUID - type – only the PostgreSQL driver returns values of corresponding columns using this type; other servers return String. To abstract application code from the database type, it is recommended to convert parameter types and query results using the DbTypeConverter interface.

4.3.1.1. Support for Other DBMSs

In the application project, you can use any DBMS supported by the ORM framework (EclipseLink). Follow the steps below:

  • Specify the type of database in the form of an arbitrary code in the cuba.dbmsType property. The code must be different from those used in the platform: hsql, postgres, mssql, oracle.

  • Implement the DbmsFeatures, SequenceSupport, DbTypeConverter interfaces by classes with the following names: TypeDbmsFeatures, TypeSequenceSupport, and TypeDbTypeConverter, respectively, where Type is the DBMS type code. The package of the implementation class must be the same as of the interface.

  • If the project includes the workflow base project, override the CubaJbpmSpringHelper bean and its getHibernateDialectName() method to select the Hibernate dialect that will be used in jBPM.

  • Create database init and update scripts in the directories marked with the DBMS type code. Init scripts must create all database objects required by the platform entities (you can copy them from the existing 10-cuba, etc. directories and modify for your database).

  • To create and update the database by Gradle tasks, you need to specify the additional parameters for these tasks in build.gradle:

    task createDb(dependsOn: assemble, type: CubaDbCreation) {
        dbms = 'my'                                            // DBMS code
        driver = 'net.my.jdbc.Driver'                          // JDBC driver class
        dbUrl = 'jdbc:my:myserver://192.168.47.45/mydb'        // Database URL
        masterUrl = 'jdbc:my:myserver://192.168.47.45/master'  // URL of a master DB to connect to for creating the application DB
        dropDbSql = 'drop database mydb;'                      // Drop database statement
        createDbSql = 'create database mydb;'                  // Create database statement
        timeStampType = 'datetime'                             // Date and time datatype - needed for SYS_DB_CHANGELOG table creation
        dbUser = 'sa'
        dbPassword = 'saPass1'
    }
    
    task updateDb(dependsOn: assemble, type: CubaDbUpdate) {
        dbms = 'my'                                            // DBMS code
        driver = 'net.my.jdbc.Driver'                          // JDBC driver class
        dbUrl = 'jdbc:my:myserver://192.168.47.45/mydb'        // Database URL
        dbUser = 'sa'
        dbPassword = 'saPass1'
    }
4.3.1.2. DBMS Version

In addition to cuba.dbmsType application property, there is an optional cuba.dbmsVersion property. It affects the choice of interface implementations for DbmsFeatures, SequenceSupport, DbTypeConverter, and the search for database init and update scripts.

The name of the implementation class of the integration interface is constructed as follows: TypeVersionName. Here, Type is the value of the cuba.dbmsType property (capitalized), Version is the value of cuba.dbmsVersion, and Name is the interface name. The package of the class must correspond to that of the interface. If a class with the same name is not available, an attempt is made to find a class with the name without version: TypeName. If such class does not exist either, an exception is thrown.

For example, the com.haulmont.cuba.core.sys.persistence.Mssql2012SequenceSupport class is defined in the platform. This class will take effect if the following properties are specified in the project:

cuba.dbmsType = mssql
cuba.dbmsVersion = 2012

The search for database init and update scripts prioritizes the type-version directory over the type directory. This means that the scripts in the type-version directory replace the scripts with the same name in the type directory. The type-version directory can also contain some scripts with unique names; they will be added to the common set of scripts for execution, too. Script sorting is performed by path, starting with the first subdirectory of the type or type-version directory, i.e. regardless of the directory where the script is located (versioned or not).

For example, the init script for Microsoft SQL Server versions below and above 2012 should look as follows:

modules/core/db/init/
   mssql/
       10.create-db.sql
       20.create-db.sql
       30.create-db.sql
   mssql-2012/
       10.create-db.sql

4.3.2. Scripts to Create and Update the Database

A CUBA-application project always contains two sets of scripts:

  • Scripts to create the database, intended for the creation of the database from scratch. They contain a set of the DDL and DML operators, which create an empty database schema that is fully consistent with the current state of the data model of the application. These scripts can also fill the database with the necessary initialization data.

  • Scripts to update the database, intended for bringing the database structure to the current state of the data model from any of the previous states.

When changing the data model, it is necessary to reproduce the corresponding change of the database schema in create and update scripts. For example, when adding the address attribute to the Customer entity, it is necessary to:

  1. Change the table creation script:

    create table SALES_CUSTOMER (
      ID varchar(36) not null,
      CREATE_TS timestamp,
      CREATED_BY varchar(50),
      NAME varchar(100),
      ADDRESS varchar(200), -- added column
      primary key (ID)
    )
  2. Add an update script, which modifies the same table:

    alter table SALES_CUSTOMER add ADDRESS varchar(200)

The create scripts are located in the /db/init directory of the core module. For each type of DBMS supported by the application, a separate set of scripts is created and located in the subdirectory specified in cuba.dbmsType application property, for example /db/init/postgres. Create scripts names should have the following format {optional_prefix}create-db.sql.

The update scripts are located in the /db/update directory of the core module. For each type of DBMS supported by the application, a separate set of scripts is created and located in the subdirectory specified in cuba.dbmsType application property, for example, /db/update/postgres.

The update scripts can be of two types: with the *.sql or *.groovy extension. The primary way to update the database is with SQL scripts. Groovy scripts are only executed by the server mechanism to launch database scripts, Therefore they are mainly used at the production stage, in cases when migration or import of the data that cannot be implemented in pure SQL.

The update scripts should have names, which form the correct sequence of their execution when sorted in the alphabetical order (usually, it is a chronological sequence of their creation). Therefore, when creating such scripts manually, it is recommended to specify the name of the update scripts in the following format: {yymmdd}-{description}.sql, where yy is a year, mm is a month, dd is a day, and description is a short description of the script. For example, 121003-addCodeToCategoryAttribute.sql. Studio also adheres to this format when generating scripts automatically.

It is possible to group update scripts into subdirectories, however the path to the script with the subdirectory should not break the chronological sequence. For example, subdirectories can be created by using year, or by year and month.

In a deployed application, the scripts to create and update the database are located in a special database script directory, that is set by the cuba.dbDir application property.

4.3.2.1. The Structure of SQL Scripts

Create and update SQL scripts are text files with a set of DDL and DML commands separated by the "^" character. The "^" character is used, so that the ";" separator can be applied as part of complex commands; for example, when creating functions or triggers. The script execution mechanism splits the input file into separate commands using the "^" separator and executes each command in a separate transaction. This means that, if necessary, it is possible to group several single statements (e.g., insert), separated by semicolons and ensure that they execute in a single transaction.

Tip

The "^" delimiter can be escaped by doubling it. For example, if you want to pass ^[0-9\s]+$ to a statement, the script should contain ^^[0-9\s]+$.

An example of the update SQL script:

create table LIBRARY_COUNTRY (
  ID varchar(36) not null,
  CREATE_TS time,
  CREATED_BY varchar(50),
  NAME varchar(100) not null,
  primary key (ID)
)^

alter table LIBRARY_TOWN add column COUNTRY_ID varchar(36) ^
alter table LIBRARY_TOWN add constraint FK_LIBRARY_TOWN_COUNTRY_ID foreign key (COUNTRY_ID) references LIBRARY_COUNTRY(ID)^
create index IDX_LIBRARY_TOWN_COUNTRY on LIBRARY_TOWN (COUNTRY_ID)^
4.3.2.2. The Structure of Groovy scripts

Groovy update scripts have the following structure:

  • The main part, which contains the code executed before the start of the application context. In this section, you can use any Java, Groovy and the Middleware application block classes. However, it should be kept in mind that no beans, infrastructure interfaces and other application objects have yet been instantiated and it is impossible to use them.

    The main part is primarily designed to update the database schema, as usually done with ordinary SQL scripts.

  • The PostUpdate part – a set of closures, which will be executed after the start of the application context and once the update process is finished. Inside these closures, it is possible to use any Middleware objects.

    In this part of the script, it is convenient to perform data import as it is possible to use the Persistence interface and data model objects.

The execution mechanism passes the following variables to the Groovy scripts:

  • ds – instance of javax.sql.DataSource for the application database;

  • log – instance of org.apache.commons.logging.Log to output messages in the server log;

  • postUpdate – object that contains the add(Closure closure) method to add PostUpdate closures described above.

Warning

Groovy scripts are executed only by the server mechanism to launch database scripts.

An example of the Groovy update script:

import com.haulmont.cuba.core.Persistence
import com.haulmont.cuba.core.global.AppBeans
import com.haulmont.refapp.core.entity.Colour
import groovy.sql.Sql

log.info('Executing actions in update phase')

Sql sql = new Sql(ds)
sql.execute """ alter table MY_COLOR add DESCRIPTION varchar(100); """

// Add post update action
postUpdate.add({
    log.info('Executing post update action using fully functioning server')

    def p = AppBeans.get(Persistence.class)
    def tr = p.createTransaction()
    try {
        def em = p.getEntityManager()

        Colour c = new Colour()
        c.name = 'yellow'
        c.description = 'a description'

        em.persist(c)
        tr.commit()
    } finally {
        tr.end()
    }
})

4.3.3. The Execution of Database Scripts by Gradle Tasks

This mechanism is generally used by application developers for updating their own database instance. The execution of scripts essentially comes down to running a special Gradle task from build.gradle build script. This can be done from the command line or via the Studio interface.

To run scripts to create the database, the createDb task is used. In Studio, it corresponds to the RunCreate database command in main menu. When this task is started, the following occurs:

  1. Platform’s base projects scripts and db/**/*.sql scripts of the core module of the current project are built in the modules/core/build/db directory. Sets of scripts for base projects are located in subdirectories with numeric prefixes starting from 10. Scripts of the current project are located in a subdirectory with prefix 50. The numeric prefixes are used to provide the alphabetical order of the execution of scripts – first, cuba scripts are executed, then base projects scripts, then current project scripts.

  2. If the database exists, it is completely erased. A new empty database is created.

  3. All creation scripts from modules/core/build/db/init/**/*create-db.sql subdirectory are executed sequentially in the alphabetical order, and their names along with the path relative to the db directory are registered in the SYS_DB_CHANGELOG table.

  4. Similarly, in the SYS_DB_CHANGELOG table, all currently available modules/core/build/db/update/**/*.sql update scripts are registered. This is required for applying the future incremental updates to the database.

To run scripts to update the database, the updateDb task is used. In Studio, it corresponds to the RunUpdate database command in main menu. When this task is started, the following occurs:

  1. The scripts are built in similar way to createDb command described above.

  2. The execution mechanism checks, whether all base projects have required tables in the database. If the database is not initialized for use of some base project, its creation scripts are executed.

  3. A search is performed in modules/core/build/db/update/** directories, for update scripts, which are not registered in the SYS_DB_CHANGELOG table, i.e., not previously executed.

  4. All scripts found in the previous step are executed sequentially in the alphabetical order, and their names along with the path relative to the db directory are registered in the SYS_DB_CHANGELOG table.

4.3.4. The Execution of Database Scripts by the Server

The mechanism to execute database scripts by the server is used for bringing the DB up to date at the start of the application server and is activated during the initialization of the Middleware block. Obviously, the application should have been built and deployed on the server – production or developer’s Tomcat instance.

Depending on the conditions described below, this mechanism either executes create or update scripts, i.e., it can initialize the DB from scratch and update it. However, unlike the Gradle createDb task described in the previous section, the database must exist to be initialized – the server does not create the DB automatically but only executes scripts on it.

The mechanism to execute scripts by the server works as follows:

  • The scripts are extracted from the database scripts directory, defined by the cuba.dbDir application property, which by default is set to tomcat/webapps/app-core/WEB-INF/db.

  • If the DB does not have the SEC_USER table, the database is considered empty and the full initialization is run using the create scripts. After executing the initialization scripts, their names are stored in the SYS_DB_CHANGELOG table. The names of all available update scripts are stored in the same table, without their execution.

  • If the DB has the SEC_USER table but does not have the SYS_DB_CHANGELOG table (this is the case when the described mechanism is launched for the first time on the existing production DB),no scripts are executed. Instead, the SYS_DB_CHANGELOG table is created and the names of all currently available create and update scripts are stored.

  • If the DB has both the SEC_USER and SYS_DB_CHANGELOG tables, the update scripts whose names were not previously stored in the SYS_DB_CHANGELOG table are executed and their names are stored in the SYS_DB_CHANGELOG table. The sequence of scripts execution is determined by two factors: the priority of the base project (see database scripts directory: 10-cuba, 20-workflow, …​) and the name of the script file (taking into account the subdirectories of the update directory) in the alphabetical order.

    Before the execution of update scripts, the check is performed, whether all base projects have required tables in the database. If the database is not initialized for use of some base project, its creation scripts are executed.

The mechanism to execute the scripts on server startup is enabled by the cuba.automaticDatabaseUpdate application property.

In already running application, the script execution mechanism can be launched using the app-core.cuba:type=PersistenceManager JMX bean by calling its updateDatabase() method with the update parameter. Obviously it is only possible to update already existing DB as it is impossible to log in to the system to run a method of the JMX bean with an empty DB. Please note, that an unrecoverable error will occur, if part of the data model no longer corresponding to the outdated DB schema is initialized during Middleware startup or user login. That is why the automatic update of the DB on the server startup before initializing the data model is only universal.

The JMX app-core.cuba:type=PersistenceManager bean has one more method related to the DB update mechanism: findUpdateDatabaseScripts(). It returns a list of new update scripts available in the directory and not registered in the DB (not yet executed).

Recommendations for usage of the server DB update mechanism can be found in Creating and Updating the Database in Production.

4.4. Middleware Components

The following figure shows the main components of the CUBA application middle tier.

Middleware
Figure 13. Components of the Middle Tier

Services are container-managed components that form the application boundary and provide the interface to the client tier. Services may contain the business logic themselves or delegate the execution to managed beans.

Managed beans are container-managed components that contain the business logic of the application. They are called by services, other beans or via the optional JMX interface.

Persistence is the infrastructure interface to access the data storage functionality: ORM and transactions management.

4.4.1. Services

Services form the layer that defines a set of Middleware operations available to the client tier. Services encapsulate business logic and transaction management.

The main objectives of services:

  • Provide the remote interface to call from the client tier.

  • Check the availability of the active user session, that corresponds to the session identifier transferred from the client.

  • Log unhandled middleware exceptions.

In addition, it is recommended to perform user authorization in the service layer, i.e. to check their rights for a particular functionality.

The objectives common to all services are handled as follows:

  • Checking the availability of the user session and logging exceptions are performed by the ServiceInterceptor, class which intercepts the execution of each service method using Spring AOP.

  • The remote interface to access the service through Spring HTTP Invoker is created by the RemoteServicesBeanCreator, bean, which is configured in the remoting-spring.xml file of the core module.

MiddlewareServices
Figure 14. The Service Class Diagram
4.4.1.1. Creating a Service

The name of service interface should end with Service, the names of implementation class – with ServiceBean.

The following steps are required for creating a service:

  1. Create the service interface in the global module, as the service interface must be available at all tiers), and specify the service name in it. It is recommended to specify the name in the following format: {project_name}_{interface_name}. For example:

    package com.sample.sales.core;
    
    import com.sample.sales.entity.Order;
    
    public interface OrderService {
        String NAME = "sales_OrderService";
    
        void calculateTotals(Order order);
    }
  2. Create the service class in the core module and add the @org.springframework.stereotype.Service annotation to it with the name specified in the interface:

    package com.sample.sales.core;
    
    import com.sample.sales.entity.Order;
    import org.springframework.stereotype.Service;
    
    @Service(OrderService.NAME)
    public class OrderServiceBean implements OrderService {
        @Override
        public void calculateTotals(Order order) {
        }
    }

The service class, being a managed bean, should be placed inside the package tree with the root specified in the context:component-scan element of the spring.xml file. In this case, the spring.xml file contains the element:

<context:component-scan base-package="com.sample.sales"/>

which means that the search for annotated beans for this application block will be performed starting with the com.sample.sales package.

Warning

Services are only intended for calling from outside of Middleware. Do not call service methods from other components of the middle tier. An error message is logged upon detection of a service call from another service.

If different services or other Middleware components require calling the same business logic, it should be extracted and encapsulated inside an appropriate managed bean. For example:

// service interface
public interface SalesService {
    String NAME = "sample_SalesService";

    BigDecimal calculateSales(UUID customerId);
}
// service implementation
@Service(SalesService.NAME)
public class SalesServiceBean implements SalesService {

    @Inject
    private SalesCalculator salesCalculator;

    @Transactional
    @Override
    public BigDecimal calculateSales(UUID customerId) {
        return salesCalculator.calculateSales(customerId);
    }
}
// managed bean encapsulating business logic
@Component
public class SalesCalculator {

    @Inject
    private Persistence persistence;

    public BigDecimal calculateSales(UUID customerId) {
        Query query = persistence.getEntityManager().createQuery(
                "select sum(o.amount) from sample$Order o where o.customer.id = :customerId");
        query.setParameter("customerId", customerId);
        return (BigDecimal) query.getFirstResult();
    }
}
4.4.1.2. Using a Service

In order to call a service, the corresponding proxy object should be created in the client block of the application. Declare the service name and interface in the parameters of the proxy object factory to achieve this. For the Web Client block, it is WebRemoteProxyBeanCreator, for Web Portal – PortalRemoteProxyBeanCreator , for Desktop Client – RemoteProxyBeanCreator .

The proxy object factory is configured in spring.xml of the corresponding client block.

For example, to call the sales_OrderService service from the web client in the sales application, it is necessary to add the following code into the web-spring.xml file of the web module:

<bean id="sales_proxyCreator" class="com.haulmont.cuba.web.sys.remoting.WebRemoteProxyBeanCreator">
    <property name="clusterInvocationSupport" ref="cuba_clusterInvocationSupport"/>
    <property name="remoteServices">
        <map>
            <entry key="sales_OrderService" value="com.sample.sales.core.OrderService"/>
        </map>
    </property>
</bean>

All imported services are declared in a single remoteServices property in the map/entry elements.

From the application code perspective, the service’s proxy object at the client level is a standard Spring bean and can be obtained either by injection or through AppBeans class. For example:

@Inject
private OrderService orderService;

public void calculateTotals() {
    orderService.calculateTotals(order);
}

or

public void calculateTotals() {
    AppBeans.get(OrderService.class).calculateTotals(order);
}
4.4.1.3. DataService

DataService provides a facade for calling DataManager middleware implementation from the client tier. The usage of DataService interface in the application code is not recommended. Instead, use DataManager directly on both middle and client tiers.

4.4.2. System Authentication

When executing user requests, the Middleware program code always has access to the information on the current user via the UserSessionSource interface. This is possible because the corresponding SecurityContext object is automatically set for the current thread when a request is received from the client tier.

However, there are situations when the current thread is not associated with any system user, for example, when calling a bean’s method from the scheduler, or via the JMX interface. In case the bean modifies entities in the database, it will require information on who is making changes, i.e., authentication.

This kind of authentication is called "system authentication" as it requires no user participation – the application middle layer simply creates or uses an existing user session and sets the corresponding SecurityContext object for the current thread.

The following methods can be used to provide the system authentication for a code block:

  • Make use of the com.haulmont.cuba.security.app.Authentication bean:

    @Inject
    protected Authentication authentication;
    ...
    authentication.begin();
    try {
        // authenticated code
    } finally {
        authentication.end();
    }
  • Add the @Authenticated annotation to the bean method:

    @Authenticated
    public String foo(String value) {
        // authenticated code
    }

The second case uses the Authentication bean implicitly, via the AuthenticationInterceptor object, which intercepts calls of all bean methods with the @Authenticated annotation.

In the examples above, the user session will be created on behalf of the user, whose login is specified in the cuba.jmxUserLogin application property. If authentication on behalf of another user is required, pass the login of the desired user to the begin() method of the first variant.

Warning

If current thread has an active user session assigned at the time of Authentication.begin() execution, it will not be replaced. Therefore the code will be executed with the existing session and the subsequent call to the end() method will not clear the thread.

For example, if a bean is in the same JVM as the Web Client block, to which the user is currently connected, the call of the JMX bean method from the Web Client built-in JMX console will be executed on behalf of the currently logged in user, regardless of the system authentication.

4.4.3. The Persistence Interface

The infrastructure interface, serving as an entry point to data storage functionality.

The interface has the following methods:

  • createTransaction(), getTransaction() – obtain the interface to manage transactions.

  • callInTransaction(), runInTransaction() - execute an action in a new transaction with or without return value.

  • isInTransaction() – checks if an active transaction exists at the moment.

  • getEntityManager() – returns the EntityManager instance bound to the current transaction.

  • isSoftDeletion() – allows you to determine if the soft deletion mode is active.

  • setSoftDeletion() – enables or disables the soft deletion mode. Setting this property affects all newly created EntityManager. instances. Soft deletion is enabled by default.

  • getDbTypeConverter() – returns the DbTypeConverter instance for the currently used database.

  • getDataSource() – returns the javax.sql.DataSource instance for the currently used database.

    Warning

    For all javax.sql.Connection objects obtained through getDataSource().getConnection() method the close() method should be called in the finally section after using the connection. Otherwise, the connection will not be returned to the pool. Over time, the pool will overflow and the application will not be able to execute database queries.

  • getTools() – returns an instance of the PersistenceTools interface (see below).

4.4.3.1. PersistenceTools

Managed bean containing helper methods related to data storage functionality. It can be obtained either by calling the Persistence.getTools() method or like any other bean, through injection or the AppBeans class.

The PersistenceTools bean has the following methods:

  • getDirtyFields() – returns a collection of entity attribute names that have been changed since the last load of the instance from the DB. For new instances an empty collection is returned.

  • isLoaded() – determines if the specified instance attribute was loaded from the DB. The attribute may not be loaded, if it was not present in the view specified when loading the instance.

    This method only works for instances in the Managed state.

  • getReferenceId() – returns an ID of the related entity without loading it from the DB.

    Let us suppose that an Order instance was loaded in the persistent context and it is necessary to get the ID value of the Customer instance related to this Order. A call to the order.getCustomer().getId() method will execute the DB query to load the Customer instance, which in this case is unnecessary, because the value of the Customer ID is also located in the Order table as a foreign key. Whereas the execution of

    persistence.getTools().getReferenceId(order, "customer")

    will not send any additional queries to the database.

    This method works only for instances in the Managed state.

The PersistenceTools bean can be overridden in your application to extend the set of default helper methods. An example of working with the extended interface is shown below:

MyPersistenceTools tools = persistence.getTools();
tools.foo();
((MyPersistenceTools) persistence.getTools()).foo();
4.4.3.2. DbTypeConverter

The interface containing methods for conversion between data model attribute values and parameters/results of JDBC queries. An object of this interface can be obtained through the Persistence.getDbTypeConverter() method.

The DbTypeConverter interface has the following methods:

  • getJavaObject() – converts the result of the JDBC query into a type suitable for assigning to entity attribute.

  • getSqlObject() – converts the value of the entity attribute into a type suitable for assigning to the JDBC query parameter.

  • getSqlType() – returns a java.sql.Types constant that corresponds to the passed entity attribute type.

4.4.4. ORM Layer

Object-Relational Mapping is the technology for linking relational database tables to programming language objects.

Benefits of using ORM
  • Enables working with a relational DBMS by means of Java objects manipulation.

  • Simplifies programming by eliminating routine writing of SQL queries.

  • Simplifies programming by letting you load and save entire object graphs with one command.

  • Ensures easy porting of the application to different DBMS.

  • Enables use of a concise object query language – JPQL.

Shortcomings
  • Requires understanding of how ORM works.

  • Makes direct optimization of SQL and use of the DBMS specifics difficult.

CUBA uses the ORM implementation based on the EclipseLink framework.

4.4.4.1. EntityManager

EntityManager – main ORM interface for working with persistent entities.

Reference to EntityManager may be obtained via the Persistence interface by calling its getEntityManager() method. The retrieved instance of EntityManager is bound to the current transaction, i.e. all calls to getEntityManager() as part of one transaction return one and the same instance of EntityManager. After the end of the transaction using the corresponding EntityManager instance is impossible.

An instance of EntityManager contains a persistence context – a set of instances loaded from the database or newly created. The persistence context is a data cache within a transaction. EntityManager automatically flushes to the database all changes made in its persistence context on the transaction commit or when the EntityManager.flush() method is called.

The EntityManager interface used in CUBA applications mainly copies the standard javax.persistence.EntityManager interface. Let us have a look at its main methods:

  • persist() – adds a new instance of the entity to the persistence context. When the transaction is committed a corresponding record is created in DB using SQL INSERT.

  • merge() – copies the state of detached instance to the persistence context the following way: an instance with the same identifier gets loaded from DB and the state of the passed Detached instance is copied into it and then the loaded Managed instance is returned. After that you should work with the returned Managed instance. The state of this entity will be stored in DB using SQL UPDATE on transaction commit.

  • remove() – removes an object from the database, or, if soft deletion mode is turned on, sets deleteTs and deletedBy attributes.

    If the passed instance is in Detached state, merge() is performed first.

  • find() – loads an entity instance by its identifier.

    When forming a request to the database the system considers the view which has been passed as a parameter to this method. As a result, the persistence context will contain a graph of objects with all view attributes loaded.

    Tip

    Unlike for DataManager, all local attributes are loaded regardless of what is specified in the view. In EntityManager, the view affects only reference attributes.

  • createQuery() – creates a Query or TypedQuery object for executing a JPQL query.

  • createNativeQuery() – creates a Query object to execute an SQL query.

  • reload() – reloads the entity instance with the provided view.

  • isSoftDeletion() – checks if the EntityManager is in soft deletion mode.

  • setSoftDeletion() – sets soft deletion mode for this EntityManager.

  • getConnection() – returns a java.sql.Connection, which is used by this instance of EntityManager, and hence by the current transaction. Such connection does not need to be closed, it will be closed automatically when the transaction is complete.

  • getDelegate() – returns javax.persistence.EntityManager provided by the ORM implementation.

Example of using EntityManager in a service:

@Service(SalesService.NAME)
public class SalesServiceBean implements SalesService {

    @Inject
    private Persistence persistence;

    @Override
    public BigDecimal calculateSales(UUID customerId) {
        BigDecimal result;
        // start transaction
        try (Transaction tx = persistence.createTransaction()) {
            // get EntityManager for the current transaction
            EntityManager em = persistence.getEntityManager();
            // create and execute Query
            Query query = em.createQuery(
                    "select sum(o.amount) from sample$Order o where o.customer.id = :customerId");
            query.setParameter("customerId", customerId);
            result = (BigDecimal) query.getFirstResult();
            // commit transaction
            tx.commit();
        }
        return result != null ? result : BigDecimal.ZERO;
    }
}
Tip

See DataManager vs. EntityManager for information on differences between DataManager and EntityManager.

4.4.4.2. Entity States
New

An instance which has just been created in memory: Car car = new Car().

A new instance may be passed to EntityManager.persist() to be stored to the database, in which case it changes its state to Managed.

Managed

An instance loaded from the database, or a new one passed to EntityManager.persist(). Belongs to a EntityManager instance, i.e. is contained in its persistence context.

Any changes of the instance in Managed state will be saved to the database when the transaction that the EntityManager belongs to is committed.

Detached

An instance loaded from the database and detached from its persistence context (as a result of the transaction end or serialization).

The changes applied to a Detached instance will be saved to the database only if this instance becomes Managed by being passed to EntityManager.merge().

4.4.4.3. Lazy Loading

Lazy loading (loading on demand) enables delayed loading of linked entities, i.e. they get loaded when their properties are accessed for the first time.

Lazy loading generates more database queries than eager fetching, but it is stretched in time.

  • For example, in case of lazy loading of a list of N instances of entity A, each containing a link to an instance of entity B, will require N+1 requests to DB.

  • In most cases, minimizing the number of requests to the database results in less response time and database load. The platform uses the mechanism of views to achieve this. Using view allows ORM to create only one request to the database with table joining for the above mentioned case.

Lazy loading works only for instances in Managed state, i.e. within the transaction which loaded the instance.

4.4.4.4. Executing JPQL Queries

The Query interface is designed to execute JPQL queries. The reference to it may be obtained from the current EntityManager instance by calling createQuery() method. If the query is supposed to be used to load entities, we recommend calling createQuery() with the result type as a parameter. This will create a TypedQuery instance.

The methods of Query mainly correspond to the methods of the standard JPA javax.persistence.Query interface. Let us have a look at the differences.

  • setParameter() – sets a value to a query parameter. If the value is an entity instance, implicitly converts the instance into its identifier. For example:

    Customer customer = ...;
    TypedQuery<Order> query = entityManager.createQuery(
        "select o from sales$Order o where o.customer.id = ?1", Order.class);
    query.setParameter(1, customer);

    Note that the entity is passed as a parameter while comparison in the query is done using identifier.

    A variant of the method with implicitConversions = false does not perform such conversion.

  • setView(), addView() – define a view which is used to load data.

  • getDelegate() – returns an instance of javax.persistence.Query, provided by the ORM implementation.

When a request is executed through Query, changes in the current persistence context are ignored, i.e. the query just runs on the database. If the results are the instances already contained in the persistence context, the query result will contain instances from context and not the ones read from the database. The following test fragment should clarify this:

TypedQuery<User> query;
List<User> list;

query = em.createQuery("select u from sec$User u where u.name = ?1", User.class);
query.setParameter(1, "testUser");
list = query.getResultList();
assertEquals(1, list.size());
User user = list.get(0);

user.setName("newName");

query = em.createQuery("select u from sec$User u where u.name = ?1", User.class);
query.setParameter(1, "testUser");
list = query.getResultList();
assertEquals(1, list.size());
User user1 = list.get(0);

assertTrue(user1 == user);

Queries that change data (update, delete) cause flush of the current persistence context to the database prior to execution. In other words, ORM first synchronizes the states of entities in the persistence context and in the database, and only after that runs the modifying query. We recommend to run such queries in an unchanged persistence context in order to prevent implicit actions by ORM, which may have negative impact on performance.

4.4.4.4.1. Case-Insensitive Substring Search

You can use the (?i) prefix in the value of the query parameters to conveniently specify conditions for case insensitive search by any part of the string. For example, look at the query:

select c from sales$Customer c where c.name like :name

If you pass the string (?i)%doe% as a value of the name parameter, the search will return John Doe, if such record exists in the database, even though the case of symbols is different. This will happen because ORM will run the SQL query with the condition lower(C.NAME) like ?.

It should be kept in mind that such search will not use index on the name field, even if such exists in the database.

4.4.4.4.2. Macros in JPQL

JPQL query text can include macros, which are processed before the query is executed. They are converted into the executable JPQL and can additionally modify the set of query parameters.

The macros solve the following problems:

  • Provide a workaround for the limitation of JPQL which makes it impossible to express the condition of dependency of a given field on current time (i.e. expressions like "current_date -1" do not work).

  • Enable comparing Timestamp type fields (the date/time fields) with a date.

Let us consider them in more detail:

@between

Has the format @between(field_name, moment1, moment2, time_unit), where

  • field_name is the name of the compared attribute.

  • moment1, moment2 – start and end points of the time interval where the value of field_name should fall into. Each of the points should be defined by an expression containing now variable with an addition or subtraction of an integer number.

  • time_unit – defines the unit for time interval added to or subtracted from now in the time point expressions and time points rounding precision. May be one of the following: year, month, day, hour, minute, second. With included workflow base project, work time units can also be used: workday, workhour, workminute.

The macro gets converted to the following expression in JPQL: field_name >= :moment1 and field_name < :moment2

Example 1. Customer was created today:

select c from sales$Customer where @between(c.createTs, now, now+1, day)

Example 2. Customer was created within the last 10 minutes:

select c from sales$Customer where @between(c.createTs, now-10, now, minute)

Example 3. Documents dated within the last 5 work days (for the projects including workflow):

select d from sales$Doc where @between(d.createTs, now-5, now, workday)
@today

Has the format @today(field_name) and helps to define a condition checking that the attribute value falls into the current date. Essentially, this is a special case of the @between macro.

Example. Customer was created today:

select d from sales$Doc where @today(d.createTs)
@dateEquals

Has the format @dateEquals(field_name, parameter) and allows you to define a condition checking that field_name value (in Timestamp format) falls into the date passed as parameter.

Example:

select d from sales$Doc where @dateEquals(d.createTs, :param)
@dateBefore

Has the format @dateBefore(field_name, parameter) and allows you to define a condition checking that field_name value (in Timestamp format) is smaller than the date passed as parameter.

Example:

select d from sales$Doc where @dateBefore(d.createTs, :param)
@dateAfter

Has the format @dateAfter(field_name, parameter) and allows you to define a condition that the date of the field_name value (in Timestamp format) is more or equal to the date passed as parameter.

Example:

select d from sales$Doc where @dateAfter(d.createTs, :param)
@enum

Allows you to use a fully qualified enum constant name instead of its database identifier. This simplifies searching for enum usages throughout the application code.

Example:

select r from sec$Role where r.type = @enum(com.haulmont.cuba.security.entity.RoleType.SUPER) order by r.name
4.4.4.5. Running SQL Queries

ORM enables execution of SQL queries returning either the lists of individual fields or entity instances. To do this, create a Query or TypedQuery object by calling one of the EntityManager.createNativeQuery() methods.

If individual columns are selected, the resulting list will include the rows as Object[]. For example:

Query query = persistence.getEntityManager().createNativeQuery(
        "select ID, NAME from SALES_CUSTOMER where NAME like ?1");
query.setParameter(1, "%Company%");
List list = query.getResultList();
for (Iterator it = list.iterator(); it.hasNext(); ) {
    Object[] row = (Object[]) it.next();
    UUID id = (UUID) row[0];
    String name = (String) row[1];
}

If a single column or aggregate function is selected, the result list will contain these values directly:

Query query = persistence.getEntityManager().createNativeQuery(
        "select count(*) from SEC_USER where login = #login");
query.setParameter("login", "admin");
long count = (long) query.getSingleResult();

If the resulting entity class is passed to EntityManager.createNativeQuery() along with the query text, TypedQuery is returned, and ORM attempts to map the query results to entity attributes. For example:

TypedQuery<Customer> query = em.createNativeQuery(
    "select * from SALES_CUSTOMER where NAME like ?1",
    Customer.class);
query.setParameter(1, "%Company%");
List<Customer> list = query.getResultList();

Keep in mind when using SQL, that the columns corresponding to entity attributes of UUID type are returned as UUID or as String depending on the DBMS in use:

  • HSQLDBString

  • PostgreSQLUUID

  • Microsoft SQL ServerString

  • OracleString

  • MySQLString

Parameters of this type should also be passed either as UUID or using their string representation, depending on the DBMS. To ensure that your code does not depend on the DBMS specifics, use DbTypeConverter. It provides methods to convert data between Java objects and JDBC parameters and results.

Native queries support positional and named parameters. Positional parameters are marked in the query text with ? followed by the parameter number starting from 1. Named parameters are marked with the number sign (#). See the examples above.

Behavior of SQL queries returning entities and modifying queries (update, delete) in relation to the current persistence context is similar to that of JPQL queries described above.

4.4.4.6. Entity Listeners

Entity Listeners are designed to react to the entity instances lifecycle events on Middleware.

A listener is a class implementing one or several interfaces from the com.haulmont.cuba.core.listener package. The listener will react to events corresponding to the implemented interfaces.

BeforeDetachEntityListener

onBeforeDetach() method is called before the object is detached from EntityManager on transaction commit.

This listener can be used for populating non-persistent entity attributes before sending it to the client tier.

BeforeAttachEntityListener

onBeforeAttach() method is called before the object is attached to the persistence context as a result of EntityManager.merge() operation.

This listener can be used, for example, to populate persistent entity attributes before saving it to the database.

BeforeInsertEntityListener

onBeforeInsert() method is called before a record is inserted into the database. All kinds of operations can be performed with the current EntityManager available within this method.

AfterInsertEntityListener

onAfterInsert() is called after a record is inserted into database, but before transaction commit. This method does not allow modifications of the current persistence context, however, database modifications can be done using QueryRunner.

BeforeUpdateEntityListener

onBeforeUpdate() method is called before a record is updated in the database. All kinds of operations can be performed with the current EntityManager available within this method.

AfterUpdateEntityListener

onAfterUpdate() method is called after a record was updated in the database, but before transaction commit. This method does not allow modifications of the current persistence context, however, database modifications can be done using QueryRunner.

BeforeDeleteEntityListener

`onBeforeDelete()`method is called before a record is deleted from the database (in the case of soft deletion – before updating a record). All kinds of operations can be performed with the current EntityManager available within this method.

AfterDeleteEntityListener

onAfterDelete() method is called after a record is deleted from the database (in the case of soft deletion – before updating a record), but before transaction commit. This method does not allow modifications of the current persistence context, however, database modifications can be done using QueryRunner.

An entity listener can be a plain Java class or a managed bean. In the latter case, injection can be used as follows:

@Component("cuba_MyEntityListener")
public class MyEntityListener implements
        BeforeInsertEntityListener<MyEntity>,
        BeforeUpdateEntityListener<MyEntity> {

    @Inject
    protected Persistence persistence;

    @Override
    public void onBeforeInsert(MyEntity entity) {
        EntityManager em = persistence.getEntityManager();
        ...
    }

    @Override
    public void onBeforeUpdate(MyEntity entity) {
        EntityManager em = persistence.getEntityManager();
        ...
    }
}

Entity Listener can be created in two ways:

  • Statically – the names of listener classes are listed in @Listeners annotation on the entity class.

    @Entity(...)
    @Table(...)
    @Listeners("cuba_MyEntityListener")
    public class MyEntity extends StandardEntity {
        ...
    }
  • Dynamically – entity and listener classes are passed to addListener() method of the EntityListenerManager bean. See example in this section: Running Code at Application Start.

Only one listener instance of a certain type is created for all instances of a particular entity class, therefore listener must not have a state.

If several listeners of the same type (for example from annotations of entity class and its parents and also added dynamically) were declared for an entity, they will be called in the following order:

  1. For each ancestor, starting from the most distant one, dynamically added listeners are called first, followed by statically assigned listeners.

  2. Once parent classes are processed, dynamically added listeners for the given class are called first, followed by statically assigned.

4.4.5. Transaction Management

This section covers various aspects of transaction management in CUBA applications.

4.4.5.1. Programmatic Transaction Management

Programmatic transaction management is done using com.haulmont.cuba.core.Transaction interface, a reference to which may be obtained via the createTransaction() or getTransaction() methods of the Persistence infrastructure interface.

The createTransaction() method creates a new transaction and returns the Transaction interface. Subsequent calls of commit(), commitRetaining(), end() methods of this interface control the created transaction. If at the moment of creation there was another transaction, it will be suspended and resumed after the completion of the newly created one.

The getTransaction() method either creates a new transaction or attaches to an existing one. If at the moment of the call there is an active transaction, then the method completes successfully, but subsequent calls of commit(), commitRetaining(), end() have no influence on the existing transaction. However calling end() without a prior call to commit() will mark current transaction as RollbackOnly.

Examples of programmatic transaction management:

@Inject
private Metadata metadata;
@Inject
private Persistence persistence;
...
// try-with-resources style
try (Transaction tx = persistence.createTransaction()) {
    Customer customer = metadata.create(Customer.class);
    customer.setName("John Smith");
    persistence.getEntityManager().persist(customer);
    tx.commit();
}
// plain style
Transaction tx = persistence.createTransaction();
try {
    Customer customer = metadata.create(Customer.class);
    customer.setName("John Smith");
    persistence.getEntityManager().persist(customer);
    tx.commit();
} finally {
    tx.end();
}

Transaction interface has also the execute() method accepting an action class or a lambda expression. The action will be performed in the transaction. This enables organizing transaction management in functional style, for example:

UUID customerId = persistence.createTransaction().execute((EntityManager em) -> {
    Customer customer = metadata.create(Customer.class);
    customer.setName("ABC");
    em.persist(customer);
    return customer.getId();
});

Customer customer = persistence.createTransaction().execute(em ->
        em.find(Customer.class, customerId, "_local"));

Keep in mind that execute() method of a given instance of Transaction may be called only once because the transaction ends after the action code is executed.

4.4.5.2. Declarative Transaction Management

Any method of the Middleware managed bean may be annotated with @org.springframework.transaction.annotation.Transactional, which will automatically create a transaction when the method is called. Such method does not require invoking Persistence.createTransaction(), you can immediately get EntityManager and work with it.

@Transactional annotation supports a number of parameters. The main parameter is the transaction creation mode – Propagation. The REQUIRED value corresponds to getTransaction(), the REQUIRES_NEW value – to createTransaction(). The default value is REQUIRED.

Declarative transaction management allows you to reduce the amount of boilerplate code, but it has the following drawback: transactions are committed outside of the application code, which often complicates debugging because it conceals the moment when changes are sent to the database and the entities become Detached. Additionally, keep in mind that declarative markup will only work if the method is called by the container, i.e. calling a transaction method from another method of the same object will not start a transaction.

With this in mind, we recommend using declarative transaction management only for simple cases like a service method reading a certain object and returning it to the client.

4.4.5.3. Examples of Transactions Interaction
4.4.5.3.1. Rollback of a Nested Transaction

If a nested transaction was created via getTransaction() and rolled back, then commit of the enclosing transaction will be impossible. For example:

void methodA() {
    Transaction tx = persistence.createTransaction();
    try {
        // (1) calling a method creating a nested transaction
        methodB();

        // (4) at this point an exception will be thrown, because transaction
        // is marked as rollback only
        tx.commit();
    } finally {
        tx.end();
    }
}

void methodB() {
    Transaction tx = persistence.getTransaction();
    try {
        // (2) let us assume the exception occurs here
        tx.commit();
    } catch (Exception e) {
        // (3) handle it and exit
        return;
    } finally {
        tx.end();
    }
}

If the transaction in methodB() is created with createTransaction() instead, then rolling it back will have no influence on the enclosing transaction in methodA().

4.4.5.3.2. Reading and Modifying Data in a Nested Transaction

Let us first have a look at a dependent nested transaction created using getTransaction():

void methodA() {
    Transaction tx = persistence.createTransaction();
    try {
        EntityManager em = persistence.getEntityManager();

        // (1) loading an entity with name == "old name"
        Employee employee = em.find(Employee.class, id);
        assertEquals("old name", employee.getName());

        // (2) setting new value to the field
        employee.setName("name A");

        // (3) calling a method creating a nested transaction
        methodB();

        // (8) the changes are committed to DB, and
        // it will contain "name B"
        tx.commit();
    } finally {
      tx.end();
    }
}

void methodB() {
    Transaction tx = persistence.getTransaction();
    try {
        // (4) retrieving the same instance of EntityManager as methodA
        EntityManager em = persistence.getEntityManager();

        // (5) loading an entity with the same identifier
        Employee employee = em.find(Employee.class, id);

        // (6) the field value is the new one since we are working with the same
        // persistent context, and there are no calls to DB at all
        assertEquals("name A", employee.getName());
        employee.setName("name B");

        // (7) no actual commit is done at this point
        tx.commit();
    } finally {
      tx.end();
    }
}

Now, let us have a look at the same example with an independent nested transaction created with createTransaction():

void methodA() {
    Transaction tx = persistence.createTransaction();
    try {
        EntityManager em = persistence.getEntityManager();

        // (1) loading an entity with name == "old name"
        Employee employee = em.find(Employee.class, id);
        assertEquals("old name", employee.getName());

        // (2) setting new value to the field
        employee.setName("name A");

        // (3) calling a method creating a nested transaction
        methodB();

        // (8) an exception occurs due to optimistic locking
        // and commit will fail
        tx.commit();
    } finally {
      tx.end();
    }
}

void methodB() {
    Transaction tx = persistence.createTransaction();
    try {
        // (4) creating a new instance of EntityManager,
        // as this is a new transaction
        EntityManager em = persistence.getEntityManager();

        // (5) loading an entity with the same identifier
        Employee employee = em.find(Employee.class, id);

        // (6) the field value is old because an old instance of the entity
        // has been loaded from DB
        assertEquals("old name", employee.getName());

        employee.setName("name B");

        // (7) the changes are commited to DB, and the value of
        // "name B" will now be in DB
        tx.commit();
    } finally {
      tx.end();
    }
}

In the last example, the exception at point (8) will only occur if the entity supports optimistic locking, i.e. if it implements Versioned interface.

4.4.5.4. Transaction Timeout

You can set a timeout in seconds for created transaction. When the timeout is exceeded, the transaction is interrupted and rolled back. Transaction timeout effectively limits the maximum duration of a database request.

When transactions are managed programmatically, the timeout is specified by passing TransactionParams object to the Persistence.createTransaction() method. For example:

Transaction tx = persistence.createTransaction(new TransactionParams().setTimeout(2));

In the case of declarative transactions management, timeout parameter of the @Transactional annotation can be used, for example:

@Transactional(timeout = 2)
public void someServiceMethod() {
...

The default timeout can be defined using the cuba.defaultQueryTimeoutSec application property.

4.4.6. Entity Cache

Entity cache is provided by EclipseLink ORM framework. It stores recently read or written entity instance in memory, which minimizes database access and improves the application performance.

Entity cache is used only when you retrieve entities by ID, so queries by other attributes still run on the database. However, these queries can be simpler and faster if related entities are in cache. For example, if you query for Orders together with related Customers and do not use cache, the SQL query will contain a JOIN for customers table. If Customer entities are cached, the SQL query will select only orders, and related customers will be retrieved from the cache.

In order to turn on entity cache, set the following properties in the app.properties file of your core module:

  • eclipselink.cache.shared.default = true - turns on caching of all entities.

  • eclipselink.cache.shared.sales$Customer = true - turns on caching of sales$Customer entity only.

  • eclipselink.cache.size.sales$Customer = 500 - sets cache size for sales$Customer to 500 instances. Default size is 100.

The fact of whether an entity is cached affects the fetch mode chosen by the platform for loading entity graphs. If a reference attribute is a cacheable entity, the fetch mode is always UNDEFINED, which allows ORM to retrieve the reference from the cache instead of executing queries with JOINs or separate batch queries.

The platform provides entity cache coordination in middleware cluster. When a cached entity is updated or deleted on one cluster node, the same cached instance on other nodes (if any) will be invalidated, so the next operation with this instance will read a fresh state from the database.

4.5. Generic User Interface

Generic user interface (Generic UI, GUI) subsystem allows you to create UI screens using XML and Java. The screens created using this approach work identically in both standard client blocks: Web Client and Desktop Client.

ClientStructure
Figure 15. The Structure of Generic User Interface

Main components of Generic UI screens are marked as green:

  • XML-descriptors – XML files containing information about datasources and screen layout.

  • Controllers – Java classes containing logic for screen initialization and handling of events generated by UI controls.

The code of application screens included in the gui module interacts with visual component interfaces (VCL Interfaces) implemented separately in the web and desktop modules of the cuba base project. For Web Client the implementation is based on the Vaadin framework, for Desktop Client on the Java Swing framework.

Visual Components Library (VCL)contains a large set of ready-to-use components.

Datasources mechanism provides a unified interface that ensures functioning of data-aware visual components.

Client’s infrastructure (Infrastructure) includes main application window, mechanisms for display and interaction of UI screens and means of interaction with the middleware.

4.5.1. Screens

A generic UI screen is defined by an XML-descriptor and a controller class. The descriptor has a link to the controller class.

In order to be able to invoke the screen from the main menu or from Java code (e.g. from controller of a different screen) the XML-descriptor should be registered in the project’s screens.xml file.

The main menu of an application is generated separately for the Web Client and the Desktop Client based on the menu.xml files, located in the project’s web and desktop modules.

4.5.1.1. Screen Types

This section describes the following basic types of screens:

4.5.1.1.1. Frame

Frames are parts of the screen intended for decomposition and reuse.

The frame element of the screen’s XML is used to add a frame to the screen. It defines either path to the frame’s XML descriptor, or its identifier, if the frame is registered in screens.xml.

A frame controller should be derived from the AbstractFrame class.

Rules for interaction between a screen and its enclosed frame are the following:

  • Frame components can be referenced from a screen using a dot: frame_id.component_id

  • List of screen components can be obtained from a frame controller by invoking getComponent(component_id) method but only if there is no component with the same name in the frame itself. I.e. frame components mask screen components.

  • Screen datasource can be obtained from a frame by invoking getDsContext().get(ds_id) method or injection, or using ds$ds_id in query, but only if the datasource with a matching name is not declared in the frame itself (same as for components).

  • From a screen, frame datasource can be obtained only by iterating getDsContext().getChildren() collection.

Screen commit also causes commits of modified datasources of the frame it uses.

4.5.1.1.2. Simple Screen

Simple screens enable displaying and editing of arbitrary information including individual instances and lists of entities. This screen type has only core functionality to display it in the application’s main window, close it and to work with datasources.

Screen identifier in screens.xml may have an arbitrary format.

The controller of a simple screen should be inherited from the AbstractWindow class.

4.5.1.1.3. Lookup Screen

When a lookup screen is invoked by openLookup() method, it displays a panel at the bottom with the buttons designed to pass an instance of the currently selected entity to the calling code. That is the main difference between lookup and simple screen. When being invoked by openWindow() method or, for example, from the main menu, the panel with the buttons is not displayed.

Lookup screens are recommended to be used to display lists of entities. Visual components intended to display and edit links between entities (such as PickerField, LookupPickerField, SearchPickerField) invoke lookup screens to find related entities.

For standard actions to work correctly, an identifier of a lookup screen in screens.xml should have the format of {entity_name}.lookup, for example, sales$Customer.lookup.

The controller of a lookup screen should be inherited from the AbstractLookup class. The lookupComponent attribute of the screen’s XML should refer to the component (for example Table), from which the selected entity instance should be taken as a result of the lookup.

4.5.1.1.4. Edit Screen

Edit screen is designed to display and edit entity instances. It initializes the instance being edited and supports actions for committing changes to the database. Edit screen should be opened by the openEditor() method passing an entity instance as an argument.

For standard actions to work correctly, an identifier of an edit screen inscreens.xml should have the format of {entity_name}.edit, for example, sales$Customer.edit.

Edit screen controller should be inherited from the AbstractEditor class. The datasource attribute of a screen’s XML should refer to a datasource containing the edited entity instance. The following standard button frames in the XML can be used to display actions that commit or cancel changes:

  • editWindowActions (file com/haulmont/cuba/gui/edit-window.actions.xml) – contains OK and Cancel buttons

  • extendedEditWindowActions (file com/haulmont/cuba/gui/extended-edit-window.actions.xml) – contains OK & Close, OK and Cancel

The following actions are implicitly initialized in the edit screen:

  • windowCommitAndClose (corresponds to the Window.Editor.WINDOW_COMMIT_AND_CLOSE constant) – an action committing changes to the database and closing the screen. The action is initialized if the screen has a visual component with windowCommitAndClose identifier. The action is displayed as an OK & Close button when the mentioned above standard extendedEditWindowActions frame is used.

  • windowCommit (corresponds to the Window.Editor.WINDOW_COMMIT constant) – an action which commits changes to the database. In absence of windowCommitAndClose action, closes the screen after committing. The action is always displayed as an OK button if the screen has the abovementioned standard frames.

  • windowClose (corresponds to the Window.Editor.WINDOW_CLOSE constant) – which closes the screen without committing any changes. The action is always initialized. If the screen has the abovementioned standard frames, it is displayed as Cancel button.

Thus, if the screen contains an editWindowActions frame, the OK button commits the changes and closes the screen, and the Cancel button – closes the screen without committing the changes. If the screen contains an extendedEditWindowActions frame, the OK button only commits the changes, OK & Close button commits the changes and closes the screen, and the Cancel button closes the screen without committing the changes.

Instead of standard frames actions can be visualized using arbitrary components, for example, LinkButton.

4.5.1.2. XML-Descriptor

XML-descriptor is a file in XML format describing datasources and screen layout.

Descriptor has the following structure:

window − root element.

window attributes:

  • class − name of a controller class.

  • messagesPack − a default message pack for the screen. It is used to obtain localized messages in the controller using getMessage() method and in the XML descriptor using message key without specifying the pack.

  • caption − window caption, can contain a link to a message from the above mentioned pack, for example,

    caption="msg://credits"
  • focusComponent − identifier of a component which should get input focus when the screen is displayed.

  • lookupComponent – mandatory attribute for a lookup screen; defines the identifier of a visual component that the entity instance should be selected from. Supports the following types of components (and their subclasses):

    • Table

    • Tree

    • LookupField

    • PickerField

    • OptionsGroup

  • datasource – mandatory attribute for an edit screen which defines the identifier of the datasource containing the edited entity instance.

window elements:

  • metadataContext − the element initializing the views required for the screen. It is recommended to define all views in a single views.xml file, because all view descriptors are deployed into a common repository, so it is difficult to ensure unique names if the descriptors are scattered across multiple files.

  • dsContext − defines datasource for the screen.

  • actions – defines the list of actions for the screen.

  • timers – defines the list of timers for the screen.

  • companions – defines the list of companion classes for the screen controller.

    Elements of companions:

    • web – defines a companion implemented in the web module.

    • desktop – defines a companion implemented in the desktop module.

    Each of these elements contains class attribute defining the companion class.

  • layout − root element of the screen layout, a container with a vertical layout of components, similar to vbox.

    Attributes of layout:

4.5.1.3. Screen Controller

Screen controller is a Java or Groovy class, linked to an XML-descriptor and containing screen initialization and event handling logic.

Controller should be inherited from one of the following base classes:

Tip

If a screen does not need additional logic, it can use the base class itself as a controller – AbstractWindow, AbstractLookup or AbstractEditor, by specifying it in the XML-descriptor (these classes are not actually abstract in a sense of impossibility of instantiating). For frames, controller class can be omitted.

Controller class should be registered in class attribute of the root element window in a screen’s XML descriptor.

Controllers
Figure 16. Controller Base Classes
4.5.1.3.1. AbstractFrame

AbstractFrame is the root of the controller classes hierarchy. Below is the description of its main methods:

  • init() is called by the framework after creating components tree described by an XML-descriptor, but before a screen is displayed.

    init() method accepts a map of parameters that can be used in controller. These parameters can be passed both from the controller of the calling screen (using openWindow(), openLookup() or openEditor() methods) or defined in the screen registration file screens.xml.

    init() method should be implemented if it is necessary to initialize screen components, for example:

    @Inject
    private Table someTable;
    
    @Override
    public void init(Map<String, Object> params) {
        someTable.addGeneratedColumn("someColumn", new Table.ColumnGenerator<Colour>() {
            @Override
            public Component generateCell(Colour entity) {
                ...
            }
        });
    }
  • getMessage(), formatMessage() – methods for retrieving localized messages from a pack, defined for a screen in the XML-descriptor. They work as shortcuts for calling the corresponding methods of the Messages interface.

  • openFrame() – loads a frame according to an identifier registered in screens.xml file. If the method receives a container component from the invoking code, the frame is shown within the container. The method returns frame controller. For example:

    @Inject
    private BoxLayout container;
    
    @Override
    public void init(Map&lt;String, Object&gt; params) {
        SomeFrame frame = openFrame(container, "someFrame");
        frame.setHeight("100%");
        frame.someInitMethod();
    }

    It is not required to pass the container immediately via openFrame() method, instead it is possible to load the frame first and then add it to the necessary container:

    @Inject
    private BoxLayout container;
    
    @Override
    public void init(Map<String, Object> params) {
        SomeFrame frame = openFrame(null, "someFrame");
        frame.setHeight("100%");
        frame.someInitMethod();
        container.add(frame);
    }
  • openWindow(), openLookup(), openEditor() – open a simple screen, a lookup screen, or an edit screen respectively. Methods return a controller of the created screen.

    CloseListener can be added in order to perform actions after the invoked screen closes, for example:

    CustomerEdit editor = openEditor("sales$Customer.edit", customer, WindowManager.OpenType.THIS_TAB);
    editor.addCloseListener((String actionId) -> {
        // do something
    });

    Use CloseWithCommitListener to be notified only when the invoked screen closes by an action with the Window.COMMIT_ACTION_ID name (i.e. OK button), for example:

    CustomerEdit editor = openEditor("sales$Customer.edit", customer, WindowManager.OpenType.THIS_TAB);
    editor.addCloseWithCommitListener(() -> {
        // do something
    });
  • showMessageDialog() – shows a dialog box with a message.

  • showOptionDialog() – shows a dialog box with a message and an option for user to invoke certain actions. Actions are defined by an array of Action type items displayed as buttons in the dialog.

    It is recommended to use DialogAction objects for display of standard buttons such as OK, Cancel and other, for example:

    showOptionDialog("PLease confirm", "Are you sure?",
            MessageType.CONFIRMATION,
            new Action[] {
                new DialogAction(DialogAction.Type.YES) {
                    @Override
                    public void actionPerform(Component component) {
                        // do something
                    }
                },
                new DialogAction(DialogAction.Type.NO);
            });
  • showNotification() – shows a pop up notification.

  • showWebPage() – opens specified web page in a browser.

4.5.1.3.2. AbstractWindow

AbstractWindow is a subclass of AbstractFrame and defines the following methods:

  • getDialogOptions() – returns a DialogOptions object to control geometry and behaviour of the screen when it is opened as a dialog (WindowManager.OpenType.DIALOG). See the examples below.

    Setting the width and height:

    @Override
    public void init(Map<String, Object> params) {
        getDialogOptions().setWidth(480).setHeight(320);
    }

    Making the dialog non-modal and resizable:

    @Override
    public void init(Map<String, Object> params) {
        getDialogOptions().setModal(false).setResizable(true);
    }

    Specifying that the screen should always be opened as a dialog regardless of what WindowManager.OpenType was selected in the calling code:

    @Override
    public void init(Map<String, Object> params) {
        getDialogOptions().setForceDialog(true);
    }
  • ready() - a template method that can be implemented in controller to intercept the moment of screen opening. It is invoked when the screen is fully initialized and opened.

  • validateAll() – validates a screen. The default implementation calls validate() for all screen components implementing the Component.Validatable interface, collects information about exceptions and displays corresponding message. Method returns false, if any exceptions were found; and true otherwise.

    This method should be overridden only if it is required to override screen validation procedure completely. It is sufficient to implement a special template method – postValidate(), if validation should be just supplemented.

  • postValidate() – a template method that can be implemented in controller for additional screen validation. The method stores validation errors information in ValidationErrors object which is passed to it. Afterwards this information is displayed together with the errors of standard validation. For example:

    private Pattern pattern = Pattern.compile("\\d");
    
    @Override
    protected void postValidate(ValidationErrors errors) {
        if (getItem().getAddress().getCity() != null) {
            if (pattern.matcher(getItem().getAddress().getCity()).find()) {
                errors.add("City name can't contain digits");
            }
        }
    }
  • close() – closes this screen.

    The method accepts string value, which is then passed to preClose() template method and to CloseListener listeners. Thus, the information about the reason why the window was closed can be obtained from the code that initiated the closing event. It is recommended to use the following constants for closing edit screens: Window.COMMIT_ACTION_ID after committing changes, Window.CLOSE_ACTION_ID – without committing changes.

    If any of the datasources contains unsaved changes, a dialog with a corresponding message will be displayed before the screen is closed. Notification type may be adjusted using the cuba.gui.useSaveConfirmation application property.

    A variant of close() method with force = true parameter closes the screen without calling preClose() and without a notification regardless of any unsaved changes.

    close() method returns true, if the screen is closed successfully, and false – if closing procedure was interrupted.

  • preClose() is a template method which can be implemented in a controller to intercept the moment when the window closes. The method receives a string value provided by the closing initiator when invoking close() method.

    If the preClose() method returns false, the window closing process is interrupted.

4.5.1.3.3. AbstractLookup

AbstractLookup is the base class for lookup screen controllers. It is a subclass of AbstractWindow and defines the following own methods:

  • setLookupComponent() – sets the component, which will be used to select entity instances.

    As a rule, component for selection is defined in screen XML-descriptor and there is no need to call this method in the application code.

  • setLookupValidator() – sets Window.Lookup.Validator object to the screen, which validate() method is invoked by the framework before returning selected entity instances. If validate() method returns false, the lookup and window closing process is interrupted.

    By default, the validator is not set.

4.5.1.3.4. AbstractEditor

AbstractEditor is the base class for edit screen controller. It is a subclass of AbstractWindow.

When creating a controller class, it is recommended to parameterize AbstractEditor with the edited entity class. This enables getItem() and initNewItem() methods work with the specified entity type and application code does not need to do additional type conversion. For example:

public class CustomerEdit extends AbstractEditor<Customer> {

    @Override
    protected void initNewItem(Customer item) {
        ...

AbstractEditor defines the following own methods:

  • getItem() – returns an instance of the entity being edited, which is set in the main datasource of the screen (i.e. specified in the datasource attribute of the root element of the XML-descriptor).

    If the instance being edited is not a new one, screen opening procedure will reload the instance from the database with the required view as set for the main datasource.

    Changes made to the instance returned by getItem(), are reflected in the state of the datasource and will be sent to the Middleware at commit.

    Warning

    It should be considered that getItem() returns a value only after screen is initialized with setItem() method. Until this moment, this method returns null, for instance when calling from inside init() or initNewItem().

    However, in the init() method, an instance of an entity passed to openEditor() can be retrieved from parameters using the following approach:

    @Override
    public void init(Map<String, Object> params) {
        Customer item = WindowParams.ITEM.getEntity(params);
        // do something
    }

    The initNewItem() method receives an instance as a parameter of the appropriate type.

    In both cases the obtained entity instance will be reloaded afterwards unless it is a new one. Therefore you should not change it or save it in a field for future use.

  • setItem() – invoked by the framework when a window is opened using openEditor() to set the instance being edited to the main datasource. By the moment of invocation all screen components and datasources will have been created and the controller’s init() method will have been executed.

    It is recommended to use template methods initNewItem() and postInit(), instead of overriding setItem() in order to initialize a screen.

  • initNewItem() – a template method invoked by the framework before setting the edited entity instance into the main datasource.

    The initNewItem() method is called for newly created entity instances only. The method is not called for detached instances. This method can be implemented in the controller, if new entity instances must be initialized before setting them in the datasource. For example:

    @Inject
    private UserSession userSession;
    
    @Override
    protected void initNewItem(Complaint item) {
        item.setOpenedBy(userSession.getUser());
        item.setStatus(ComplaintStatus.OPENED);
    }

    A more complex example of using the initNewItem() method can be found in development recipes section.

  • postInit() – a template method invoked by the framework immediately after the edited entity instance is set to the main datasource. In this method, getItem() can be called to return a new entity instance or an instance re-loaded during screen initialization.

    This method can be implemented in controller for final screen initialization, for example:

    @Inject
    protected EntityDiffViewer diffFrame;
    
    @Override
    protected void postInit() {
        if (!PersistenceHelper.isNew(getItem())) {
            diffFrame.loadVersions(getItem());
        }
    }
  • commit() – validates the screen and submits changes to the Middleware via DataSupplier.

    If a method is used with validate = false, commit does not perform a validation.

    It is recommended to use specialized template methods – postValidate(), preCommit() and postCommit() instead of overriding this method.

  • commitAndClose() – validates the screen, submits changes to the Middleware and closes the screen. The value of the Window.COMMIT_ACTION_ID will be passed to the preClose() method and registered CloseListener listeners.

    It is recommended to use specialized template methods – postValidate(), preCommit() and postCommit() instead of overriding this method.

  • preCommit() – a template method invoked by the framework during the commit process, after a successful validation, but before the data is submitted to the Middleware.

    This method can be implemented in controller. If the method returns false, commit process gets interrupted, as well as window closing process (if commitAndClose() was invoked). For example:

    @Override
    protected boolean preCommit() {
        if (somethingWentWrong) {
            showNotification("Something went wrong", NotificationType.WARNING);
            return false;
        }
        return true;
    }
  • postCommit() – a template method invoked by the framework at the final stage of committing changes. Method parameters are:

    • committed – set to true, if the screen had changes and they have been submitted to Middleware.

    • close – set to true, if the screen should be closed after the changes are committed.

      If the screen does not close the default implementation of this method displays a message about successful commit and invokes postInit().

      This method can be overridden in controller in order to perform additional actions after successful commit, for example:

      @Inject
      private Datasource<Driver> driverDs;
      @Inject
      private EntitySnapshotService entitySnapshotService;
      
      @Override
      protected boolean postCommit(boolean committed, boolean close) {
          if (committed) {
              entitySnapshotService.createSnapshot(driverDs.getItem(), driverDs.getView());
          }
          return super.postCommit(committed, close);
      }

The diagrams below show initialization sequence and different ways to commit changes for an edit screen.

EditorInit
Figure 17. Edit Screen Initialization
EditorCommit
Figure 18. Committing And Closing a Window With an editWindowActions Frame
ExtendedEditorCommit
Figure 19. Committing a Screen With an extendedEditWindowActions Frame
ExtendedEditorCommitAndClose
Figure 20. Committing a Screen With an extendedEditWindowActions Frame
4.5.1.3.5. Controller Dependency Injection

Dependency Injection in controllers can be used to acquire references to utilized objects. For this purpose it is required to declare either a field of the corresponding type or a write access method (setter) with an appropriate parameter type and with one of the following annotations:

  • @Inject – the simplest option, where an object for injection will be found according to the field/method type and the name of the field or attribute corresponding to the method according to JavaBeans rules.

  • @Named("someName") – explicitly defines the name of the target object.

The following objects can be injected into controllers:

  • This screen’s visual components defined in the XML-descriptor. If the attribute type is derived from Component, the system will search for a component with the corresponding name within the current screen.

  • Actions defined in the XML-descriptor – see Actions. The Action Interface.

  • Datasources defined in the XML-descriptor. If the attribute type is derived from Datasource, the system will search for a datasource with the corresponding name in the current screen.

  • UserSession. If the attribute type is UserSession, the system will inject an object of the current user session.

  • DsContext. If the attribute type is DsContext, the system will inject the DsContext of the current screen.

  • WindowContext. If the attribute type is WindowContext, the system will inject the WindowContext of the current screen.

  • DataSupplier. If the attribute type is DataSupplier, the corresponding instance will be injected.

  • Any bean defined in the context of a given client block, including:

  • If nothing of the mentioned above is appropriate and the controller has companions, a companion for the current client type will be injected, if the types match.

    It is possible to inject the parameters passed in the map to the init() method into the controller using special annotation @WindowParam. The annotation has a name attribute which contains the parameter name (a key in the map) and an optional required attribute. If required = true and the map does not contain the corresponding parameter a WARNING message is added to the log.An example of an injection of a Job-type object passed to the controller’s init() method:

    @WindowParam(name = "job", required = true)
    protected Job job;
4.5.1.3.6. Controller Companions

Controller base classes are located in the gui module of the cuba base project and do not contain references to the implementation of visual component classes (Swing or Vaadin). This allows you to use them in both types of clients.

At the same time concrete controller classes can be created in gui, web or desktop modules, depending on screen specifics and client blocks used in the project. If a controller is common for all client types but additional functionality is required for different client types, it can be implemented in so-called companion classes.

Companion class is located in client module of the corresponding client type (web or desktop) and implements an interface defined in the controller that uses the companion class. A companion class should be defined in the companions element of the screen XML-descriptor. Controller can retrieve a reference to the companion instance using injection or by invoking getCompanion(), and then pass control to the companion instance when appropriate, e.g. for extended initialization of visual components in a way specific to a given client type.

For example, on some screen, you need to initialize a table differently for web and desktop clients. Then in the screen controller located in gui module, define a companion interface and delegate the table initialization to it:

public class CustomerBrowse extends AbstractLookup {

    public interface Companion {
        void initTable(Table<Customer> table);
    }

    @Inject
    protected Table<Customer> table;
    @Inject
    protected Companion companion;

    @Override
    public void init(Map<String, Object> params) {
        if (companion != null) {
            companion.initTable(table);
        }
    }
}

Create companion implementations in web and desktop modules:

public class WebCustomerBrowseCompanion implements CustomerBrowse.Companion {
    @Override
    public void initTable(Table<Customer> table) {
        com.vaadin.ui.Table webTable = (com.vaadin.ui.Table) WebComponentsHelper.unwrap(table);
        // do something specific to Vaadin table
    }
}
public class DesktopCustomerBrowseCompanion implements CustomerBrowse.Companion {
    @Override
    public void initTable(Table<Customer> table) {
        javax.swing.JTable desktopTable = (javax.swing.JTable) DesktopComponentsHelper.unwrap(table);
        // do something specific to Swing table
    }
}

And register the implementation classes in the screen XML descriptor:

<window ...
      class="com.company.sample.gui.customers.CustomerBrowse">
  <companions>
      <web class="com.company.sample.web.customers.WebCustomerBrowseCompanion"/>
      <desktop class="com.company.sample.desktop.customers.DesktopCustomerBrowseCompanion"/>
  </companions>
  <dsContext>...</dsContext>
  <layout>...</layout>
</window>

The companion classes are located in web and desktop modules, therefore you can use WebComponentsHelper.unwrap() and DesktopComponentsHelper.unwrap() to get references to Vaadin and Swing components implementing the table.

4.5.2. Visual Components Library

4.5.2.1. Components
Components new
Figure 21. Components Diagram

Component is the parent of all visual components. It contains basic attributes to identify a component and place it within a screen.

Buttons

Button

Button

PopupButton

PopupButton

LinkButton

LinkButton

Text

Label

gui_label

Text inputs

TextField

gui_textField_data

PasswordField

gui_PasswordField

MaskedField

gui_MaskedField

TextArea

gui_TextArea

RichTextArea

gui_RichTextArea

Date inputs

DateField

gui_dateField

TimeField

gui_timeField

Selects

CheckBox

CheckBox

OptionsGroup

gui_optionsGroup

PickerField

PickerField

LookupField

LookupField

LookupPickerField

LookupPickerField

SearchPickerField

gui_searchPickerField

TwinColumn

TwinColumn

Uploads

FileUploadField

Upload

FileMultiUploadField

Tables and trees

Table

gui_table

GroupTable

gui_groupTable

TreeTable

gui_treeTable

Tree

gui_Tree

Others

FieldGroup

gui_fieldGroup

TokenList

gui_tokenList

Filter

gui_filter_mini

4.5.2.1.1. Button

A button performs an action when a user clicks on it.

Component’s XML-name: button

gui Button dia

Button component is implemented for both Web and Desktop clients.

Buttons can contain a caption, an icon, or both. The figure below shows different button types.

gui buttonTypes

An example of a button with a tooltip and a caption retrieved from a localized message pack:

<button id="textButton" caption="msg://someAction" description="Press me"/>

The button’s caption is set using the caption attribute, the tooltip – using the description attribute.

The icon attribute defines icon location in theme catalog. Detailed information on recommended icon placement is available in Themes.

Example of creating a button with an icon:

<button id="iconButton" caption="" icon="icons/save.png"/>

The button’s main function is to perform an action on a click. Controller method that should be invoked after a click can be defined using invoke attribute. The attribute value should contain name of the controller method satisfying the following conditions:

  • The method should be public.

  • The method should return void.

  • The method should not have any arguments, or should have a single argument of Component type. If the method has a Component argument, then an instance of the invoking button will be passed in it.

Below is the example of a button invoking someMethod:

<button invoke="someMethod" caption="msg://someButton"/>

A method named someMethod should be defined in the screen controller:

public void someMethod() {
    //some actions
}

The invoke attribute is ignored if action attribute is set. The action attribute contains the name of action corresponding to the button.

Example of a button with an action:

<actions>
    <action id="someAction" caption="msg://someAction"/>
</actions>
<layout>
    <button action="someAction"/>

Any action present in the component implementing Component.ActionsHolder interface can be assigned to a button. This applies to Table, GroupTable, TreeTable, Tree. The way of adding actions (declaratively in the XML descriptor or programmatically in the controller) is irrelevant. In any case, for using an action, the name of the component and the identifier of the required action must be specified in the action attribute, separated by dot. For instance, in the next example the create action of the coloursTable table is assigned to a button:

<button action="coloursTable.create"/>

Button actions can also be created programmatically in the screen controller by deriving them from BaseAction class.

If an Action instance is defined for a Button, the button will take the following properties from it: caption, description, icon, enable, visible. caption and description properties will be imported from Action only if they are not set in the Button itself. All other listed Action properties have priority over the Button properties. If Action properties are changed after the Action is set for a Button, then Button properties also change accordingly, i.e. the button listens to the changes in Action properties. In this case, the caption`and `description properties will change even if they was initially assigned to the button itself.


Attributes of button

action - align - caption - description - enable - icon - id - invoke - stylename - visible - width


4.5.2.1.2. BulkEditor

BulkEditor is a component that enables changing attribute values for several entity instances at once. The component is a button, usually added to a table or a tree, which opens the entity bulk editor on click.

gui bulkEdit

XML-name of the component: bulkEditor

gui BulkEditor dia

The component is implemented for Web Client and Desktop Client.

To enable the use of BulkEditor, the table or tree must have the multiselect attribute set to "true".

The entity editor is automatically generated based on the defined view (containing the fields of this entity, including references) and the user permissions. System attributes are not displayed in the editor either.

Entity attributes in the editor are sorted alphabetically. By default, the fields are empty. At screen commit, non-empty attribute values defined in the editor, are set for all the entity instances.

The editor also enables removing a specific field value for all the instances by setting it to null. In order to do this, click gui_bulkEditorSetNullButton button next to the field. After that, the field will become non-editable. The field can be unlocked by clicking the same button again.

gui invoiceBulkEdit

Example of bulkEditor use in a table:

<table id="invoiceTable"
       multiselect="true"
       width="100%">
    <actions>
        <!-- ... -->
    </actions>
    <buttonsPanel>
        <!-- ... -->
        <bulkEditor for="invoiceTable"
                    exclude="customer"/>
    </buttonsPanel>

The for attribute is required. It contains the identifier of a table or a tree; in this case, it is the invoiceTable.

The exclude attribute can contain a regular expression to exclude some fields explicitly from the list of attributes available for editing. For example: date|customer

gui TableBulkEdit

Attributes of bulkEditor

align - caption - description - enable - exclude - for - icon - id - openType - stylename - visible - width


4.5.2.1.3. CheckBox

CheckBox is a component with two states: selected or deselected.

CheckBox

Component’s XML-name: checkBox.

gui checkBox dia

CheckBox component is implemented for Web Client and Desktop Client.

An example of a checkbox with a label retrieved from a localized messages pack:

<checkBox id="accessField" caption="msg://accessFieldCaption"/>

Selecting / deselecting of the checkbox changes its value: Boolean.TRUE or Boolean.FALSE. The value can be retrieved using getValue() method and set using setValue(). Submitting null using setValue() will change the value to Boolean.FALSE and uncheck the checkbox.

Changes of the checkbox value, as well as of any other components implementing the Field interface, can be tracked using a ValueChangeListener. For example:

@Inject
private CheckBox accessField;

@Override
public void init(Map<String, Object> params) {
    accessField.addValueChangeListener(event -> {
        if (Boolean.TRUE.equals(event.getValue())) {
            showNotification("set", NotificationType.HUMANIZED);
        } else {
            showNotification("not set", NotificationType.HUMANIZED);
        }
    });
}

The datasource and property attributes should be used to create a checkbox associated with data.

<dsContext>
    <datasource id="customerDs" class="com.sample.sales.entity.Customer" view="_local"/>
</dsContext>
<layout>
    <checkBox datasource="customerDs" property="active"/>

According to the example the screen includes the description of customerDs data source for a Customer entity with active attribute. The datasource attribute of the checkBox component should contain a reference to a data source; the property attribute should contain the name of an entity attribute which value should be displayed in the checkbox. The attribute should have Boolean type. If the attribute value is null the checkbox is deselected.


Attributes of checkBox

align - caption - datasource - description - editable - enable - height - icon - id - property - stylename - visible - width


4.5.2.1.4. DateField

DateField is a field to display and enter date and time. It is an input field, inside which there is a button with a drop-down calendar. To the right, there is a time field.

gui dateFieldSimple

XML name of the component: dateField.

gui dateField dia

The DateField component is implemented for Web Client and Desktop Client.

  • To create a date field associated with data, you should use the datasource and property attributes:

    <dsContext>
        <datasource id="orderDs" class="com.sample.sales.entity.Order" view="_local"/>
    </dsContext>
    <layout>
        <dateField datasource="orderDs" property="date"/>

    In the example above, the screen has the orderDs data source for the Order entity, which has the date property. The reference to the data source is specified in the datasource attribute of the dateField component; the name of the entity attribute which value should be displayed in the field is specified in the property attribute.

  • If the field is associated with an entity attribute, it will automatically take the appropriate form:

    • If the attribute has the java.sql.Date type or the @Temporal(TemporalType.DATE) annotation is specified, the time field will not be displayed. The date format is defined by the date datatype and is specified in the main localized message pack in the dateFormat key.

    • Otherwise, the time field with hours and minutes will be displayed. The time format is defined by the time datatype and is specified in the main localized message pack in the timeFormat key.

  • You can change the date and time format using the dateFormat attribute. An attribute value can be either a format string itself or a key in a message pack (if the value starts with msg://).

    The format is defined by rules of the SimpleDateFormat class (http://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html). If there are no H or h characters in the format, the time field will not be displayed.

    <dateField dateFormat="MM/yy" caption="msg://monthOnlyDateField"/>
    gui dateField format
    Warning

    DateField is primarily intended for quick input by filling placeholders from keyboard. Therefore the component supports only formats with digits and separators. Complex formats with textual representation of weekdays or months will not work.

  • Date and time accuracy can be defined using a resolution attribute. An attribute value should match the DateField.Resolution enumeration − SEC, MIN, HOUR, DAY, MONTH, YEAR. Default is MIN, i.e., to within a minute.

    If resolution="DAY" and dateFormat is not specified, the format will be taken from one specified in the main message pack with the dateFormat key.

    If resolution="MIN" and dateFormat is not specified, the format will be taken from one specified in the main message pack with the dateTimeFormat key. Below is a field definition for entering a date up to within a month.

    <dateField resolution="MONTH" caption="msg://monthOnlyDateField"/>
gui dateField resolution
  • DateField can perform timestamp value conversions between server and user time zones if the user’s time zone is set by setTimeZone() method. The time zone is assigned automatically from the current user session when the component is bound to an entity attribute of the timestamp type. If the component is not bound to such attribute, you can call setTimeZone() in the screen controller to make the DateField perform required conversions..


Attributes of dateField

align - caption - datasource - dateFormat - description - editable - enable - height - icon - id - property - stylename - required - requiredMessage - resolution - visible - width

Elements of dateField

validator


4.5.2.1.5. Embedded

Embedded component is intended for displaying images and embedding arbitrary web pages into the application screens.

XML name of the component: embedded

gui Embedded dia

The component is implemented for Web Client and Desktop Client. Desktop Client supports image display only.

Below is an example of using the component to display an image from a file located in FileStorage.

  • Declare the component in an XML screen descriptor:

    <groupBox caption="Embedded" spacing="true"
              height="250px" width="250px" expand="embedded">
        <embedded id="embedded" width="100%"
                  align="MIDDLE_CENTER"/>
    </groupBox>
  • In a screen controller, inject the component itself and the FileStorageService interface. In init() method, get the FileDescriptor passed from the calling code, load the corresponding file in a byte array, create a ByteArrayInputStream for it, and pass the stream to the setSource() method of the component:

    @Inject
    private Embedded embedded;
    @Inject
    private FileStorageService fileStorageService;
    
    @Override
    public void init(Map<String, Object> params) {
        FileDescriptor imageFile = (FileDescriptor) params.get("imageFile");
        byte[] bytes = null;
        if (imageFile != null) {
            try {
                bytes = fileStorageService.loadFile(imageFile);
            } catch (FileStorageException e) {
                showNotification("Unable to load image file", NotificationType.HUMANIZED);
            }
        }
        if (bytes != null) {
            embedded.setSource(imageFile.getName(), new ByteArrayInputStream(bytes));
            embedded.setType(Embedded.Type.IMAGE);
        } else {
            embedded.setVisible(false);
        }
    }

In Web Client, the component enables displaying of files located inside VAADIN folder. For example:

embedded.setRelativeSource("VAADIN/themes/halo/my-logo.png")

You can also define a resource files directory in the cuba.web.resourcesRoot application property and specify the name of a file inside this directory:

embedded.setSource("my-logo.png")

In order to display an external web page, pass its URL to the component:

try {
    embedded.setSource(new URL("http://www.cuba-platform.com"));
} catch (MalformedURLException e) {
    throw new RuntimeException(e);
}

Attributes of embedded

align - height - id - stylename - visible - width


4.5.2.1.6. FieldGroup

FieldGroup is intended for the joint display and editing of multiple entity attributes.

gui fieldGroup

XML-name of the component: fieldGroup

gui FieldGroup dia

The component is implemented for Web Client and Desktop Client.

Below is an example of defining a group of fields in an XML screen descriptor:

<dsContext>
    <datasource id="orderDs"
                class="com.sample.sales.entity.Order"
                view="orderWithCustomer">
    </datasource>
</dsContext>
<layout>
    <fieldGroup id="orderFieldGroup" datasource="orderDs" width="250px">
        <field id="date"/>
        <field id="customer"/>
        <field id="amount"/>
    </fieldGroup>

In the example above, dsContext defines an orderDs data source, which contains a single instance of the Order entity. The data source is specified in the datasource attribute of the fieldGroup component. field elements refer to the entity attributes that need to be displayed in the component.

Elements of fieldGroup:

  • column – optional element that allows you to position fields in multiple columns. For this purpose, field elements should be placed not immediately within fieldGroup, but within a column. For example:

    <fieldGroup id="orderFieldGroup" datasource="orderDs" width="100%">
        <column width="250px">
            <field id="num"/>
            <field id="date"/>
            <field id="amount"/>
        </column>
        <column width="400px">
            <field id="customer"/>
            <field id="info"/>
        </column>
    </fieldGroup>

    In this case, fields will be arranged in two columns; the first column will contain all fields with the width of 250px, the second one with the width of 400px.

    Attributes of column:

    • width – specifies the field width of a column. By default, fields have the width of 200px. In this attribute, the width can be specified both in pixels and in percentage of the total horizontal width of the column.

    • flex – a number, which indicates the degree of horizontal change in the overall size of the column relative to other columns as a result of changing the entire width of fieldGroup. For example, you can specify flex=1 for a column, and flex=3 for another one.

    • id – an optional column identifier, which allows you to refer to it in case of screen extension.

  • field – the main component element. It defines one field of the component.

    Attributes of field:

    • id – required attribute; it should contain either an entity attribute name, which is displayed in the field, or an arbitrary unique identifier of a programmatically defined field. In the latter case, field should have the attribute custom="true" as well (see below).

    • caption − allows you to specify a field caption. If not specified, an entity attribute localized name will be displayed.

    • visible − allows you to hide the field together with the caption.

    • datasource − allows you to specify a data source for the field, other than specified for the entire fieldGroup component. Thus, attributes of different entities can be displayed in a field group.

    • optionsDatasource specifies a name of a data source, used to create a list of options. You can specify this attribute for a field connected to a reference entity attribute. By default, the selection of a related entity is made through a lookup screen. If optionsDatasource is specified, you can select the related entity from a drop-down list of options. Actually, specifying optionsDatasource will lead to the fact that LookupPickerField will be used in the field instead of PickerField.

    • width − allows you to specify the field width excluding caption. By default, the field width will be 200px. The width can be specified both in pixels and in percentage of the total horizontal width of the column. To specify the width of all fields simultaneously, you can use the width attribute of the column element described above.

    • custom – if set to true, it means that a field identifier does not refer to an entity attribute, and a component, which is in the field, will be set programmatically using addCustomField() method of FieldGroup (see below).

    • linkScreen - contains the identifier of the screen that is opened by clicking the link, enabled in the link attribute.

    • linkScreenOpenType - sets the screen opening mode (THIS_TAB, NEW_TAB or DIALOG).

    • linkInvoke - contains the controller method to be invoked instead of opening the screen.

    The following attributes of field can be applied depending on the type of the entity attribute displayed in the field:

    • If you specify a value of the mask attribute for a text entity attribute, MaskedField with an appropriate mask will be used instead of TextField. In this case, you can also specify the valueMode attribute.

    • If you specify a value of the rows attribute for a text entity attribute, TextArea with the appropriate number of rows will be used instead of TextField. In this case, you can also specify the cols attribute.

    • For a text entity attribute, you can specify the maxLength attribute similarly to one described for TextField.

    • For an entity attribute of the date or dateTime type, you can specify the dateFormat and resolution for the parameterization of the DateField component used in the field.

    • For an entity attribute of the time type, you can specify the showSeconds attribute for the parameterization of the TimeField component used in the field.

Attributes of fieldGroup:

  • The border attribute can be set either to hidden or visible. Default is hidden. If set to visible, the fieldGroup component is highlighted with a border. In the web implementation of the component, displaying a border is done by adding the cuba-fieldgroup-border CSS class.

Methods of the FieldGroup interface:

  • addCustomField() is used together with the custom="true" attribute of the field element and it allows you to set your own field view. It takes two parameters: field identifier specified in the id attribute of field and the implementation of the FieldGroup.CustomFieldGenerator interface.

    The generateField() method of the CustomFieldGenerator interface is invoked by FieldGroup. A data source and field identifier, for which this generator is registered, are passed into the method. The method should return a visual component (or container), which will be displayed in the field.

    Example:

    @Inject
    protected FieldGroup fieldGroup;
    @Inject
    protected ComponentsFactory componentsFactory;
    
    @Override
    public void init(Map<String, Object> params) {
        fieldGroup.addCustomField("password", new FieldGroup.CustomFieldGenerator() {
            @Override
            public Component generateField(Datasource datasource, String propertyId) {
                PasswordField passwordField = componentsFactory.createComponent(PasswordField.NAME);
                passwordField.setDatasource(datasource, propertyId);
                return passwordField;
            }
        });
    }
  • getFieldComponent() returns a visual component, which is located in a field with the specified identifier. This may be required for additional component parameterization, which is not available through XML attributes of field described above.

    To obtain a reference to a field component in a screen controller, you can use injection instead of the explicit invocation of getFieldComponent(). To do this, use the @Named annotation and provide an identifier of fieldGroup and a field identifier after a dot.

    For example, in a field selecting a related entity, you can add an action to open an instance and remove the field cleaning action as follows:

    <fieldGroup id="orderFieldGroup" datasource="orderDs">
        <field id="date"/>
        <field id="customer"/>
        <field id="amount"/>
    </fieldGroup>
    @Named("orderFieldGroup.customer")
    protected PickerField customerField;
    
    @Override
    public void init(Map<String, Object> params) {
        customerField.addOpenAction();
        customerField.removeAction(customerField.getAction(PickerField.ClearAction.NAME));
    }

    To use getFieldComponent() or to inject field components, you need to know which component type is located in the field. The table below shows the correspondence between entity attribute types and components created for them:

    Entity attribute type Additional conditions Field component type

    Related Entity

    optionsDatasource is specified

    LookupPickerField

    PickerField

    Enumeration (enum)

    LookupField

    string

    mask is specified

    MaskedField

    rows is specified

    TextArea

    TextField

    boolean

    CheckBox

    date, dateTime

    DateField

    time

    TimeField

    int, long, double, decimal

    TextField



4.5.2.1.7. FileMultiUploadField

The FileMultiUploadField component allows a user to upload files to the server. The component is a button; when it is clicked, a standard OS file picker window is shown, where the user can select multiple files for upload.

gui multipleUpload

XML name of the component: multiUpload.

gui FileMultiUploadField dia

The component is implemented for Web Client and Desktop Client.

  • Declare the component in an XML screen descriptor:

    <multiUpload id="multiUploadField" caption="msg://upload"/>
  • In the screen controller, inject the component itself, the FileUploadingAPI and DataSupplier interfaces. In the init() method, add listeners which will react on successful uploads and errors:

    @Inject
    private FileMultiUploadField multiUploadField;
    @Inject
    private FileUploadingAPI fileUploadingAPI;
    @Inject
    private DataSupplier dataSupplier;
    
    @Override
    public void init(Map<String, Object> params) {
        multiUploadField.addQueueUploadCompleteListener(() -> {
            for (Map.Entry<UUID, String> entry : multiUploadField.getUploadsMap().entrySet()) {
                UUID fileId = entry.getKey();
                String fileName = entry.getValue();
                FileDescriptor fd = fileUploadingAPI.getFileDescriptor(fileId, fileName);
                // save file to FileStorage
                try {
                    fileUploadingAPI.putFileIntoStorage(fileId, fd);
                } catch (FileStorageException e) {
                    new RuntimeException("Error saving file to FileStorage", e);
                }
                // save file descriptor to database
                dataSupplier.commit(fd);
            }
            showNotification("Uploaded files: " + multiUploadField.getUploadsMap().values(), NotificationType.HUMANIZED);
            multiUploadField.clearUploads();
        });
    
        multiUploadField.addFileUploadErrorListener(event ->
                showNotification("File upload error", NotificationType.HUMANIZED));
    }

    The component uploads all selected files to the temporary storage of the client tier and invokes the listener added by the addQueueUploadCompleteListener() method. In this listener, the FileMultiUploadField.getUploadsMap() method is invoked to obtain a map of temporary storage file identifiers to file names. Then, corresponding FileDescriptor objects are created by calling FileUploadingAPI.getFileDescriptor() for each map entry. com.haulmont.cuba.core.entity.FileDescriptor (do not confuse with java.io.FileDescriptor) is a persistent entity, which uniquely identifies an uploaded file and then is used to download the file from the system.

    FileUploadingAPI.putFileIntoStorage() method is used to move the uploaded file from the temporary client storage to FileStorage. Parameters of this method are temporary storage file identifier and the FileDescriptor object.

    After uploading the file to FileStorage, the FileDescriptor instance is saved in a database by invoking DataSupplier.commit(). The saved instance returned by this method can be set to an attribute of an entity related to this file. Here, FileDescriptor is simply stored in the database. The file will be available through the AdministrationExternal Files screen.

    After processing, the list of files should be cleared by calling the clearUploads() method in order to prepare for further uploads.

  • Maximum upload size is determined by the cuba.maxUploadSizeMb application property and is 20MB by default. If a user selects a file of a larger size, a corresponding message will be displayed, and the upload will be interrupted.

  • The accept XML attribute (and the corresponding setAccept() method) can be used to set the file type mask in the file selection dialog. Users still be able to change the mask to "All files" and upload arbitrary files.

    The value of the attribute should be a comma-separated list of masks. For example: *.jpg,*.png.

  • The permittedExtensions XML attribute (and the corresponding setPermittedExtensions() method) can be used to set permitted extensions of uploaded files. If the attribute is set, users will not be able to upload files with not permitted extensions.

    The value of the attribute should be a comma-separated list of extensions with leading dots. For example: .jpg,.jpeg,.png,.gif.

See Loading and Displaying Images for more complex example of working with uploaded files.


Attributes of multiUpload

accept - align - caption - description - enable - height - icon - id - permittedExtensions - stylename - visible - width


4.5.2.1.8. FileUploadField

The FileUploadField component allows a user to upload files to the server. The component is a button; when it is clicked, a standard OS file picker window is shown, where the user can select a file. To allow the user to upload multiple files, use FileMultiUploadField.

gui upload

XML name of the component: upload.

gui FileUploadField dia

The component is implemented for Web Client and Desktop Client.

  • Declare the component in an XML screen descriptor:

    <upload id="uploadField" caption="msg://upload"/>
  • In the screen controller, inject the component itself, the FileUploadingAPI and DataSupplier interfaces. In the init() method, add listeners which will react on successful uploads and errors:

    @Inject
    private FileUploadField uploadField;
    @Inject
    private FileUploadingAPI fileUploadingAPI;
    @Inject
    private DataSupplier dataSupplier;
    
    @Override
    public void init(Map<String, Object> params) {
        uploadField.addFileUploadSucceedListener(event -> {
            FileDescriptor fd = uploadField.getFileDescriptor();
            try {
                // save file to FileStorage
                fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd);
            } catch (FileStorageException e) {
                throw new RuntimeException("Error saving file to FileStorage", e);
            }
            // save file descriptor to database
            dataSupplier.commit(fd);
            showNotification("Uploaded file: " + uploadField.getFileName(), NotificationType.HUMANIZED);
        });
    
        uploadField.addFileUploadErrorListener(event ->
                showNotification("File upload error", NotificationType.HUMANIZED));
    }

    The component will upload the file to the temporary storage of the client tier and invoke the listener added by the addFileUploadSucceedListener() method. In this listener, a FileDescriptor object is requested from the component. com.haulmont.cuba.core.entity.FileDescriptor (do not confuse with java.io.FileDescriptor) is a persistent entity, which uniquely identifies an uploaded file and is used to download the file from the system.

    FileUploadingAPI.putFileIntoStorage() method is used to move the uploaded file from the temporary client storage to FileStorage. Parameters of this method are temporary storage file identifier and the FileDescriptor object. Both of these parameters are provided by FileUploadField.

    After uploading the file to FileStorage, the FileDescriptor instance is saved in the database by invoking DataSupplier.commit(). The saved instance returned by this method can be set to an attribute of an entity related to this file. Here, FileDescriptor is simply stored in the database. The file will be available through the AdministrationExternal Files screen.

    The listener added by the addFileUploadErrorListener() method will be invoked if an error occurs when uploading a file to the temporary storage of the client tier.

  • Maximum upload size is determined by the cuba.maxUploadSizeMb application property and is 20MB by default. If a user selects a file of a larger size, a corresponding message will be displayed, and the upload will be interrupted.

  • The accept XML attribute (and the corresponding setAccept() method) can be used to set the file type mask in the file selection dialog. Users still be able to change the mask to "All files" and upload arbitrary files.

    The value of the attribute should be a comma-separated list of masks. For example: *.jpg,*.png.

  • The permittedExtensions XML attribute (and the corresponding setPermittedExtensions() method) can be used to set permitted extensions of uploaded files. If the attribute is set, users will not be able to upload files with not permitted extensions.

    The value of the attribute should be a comma-separated list of extensions with leading dots. For example: .jpg,.jpeg,.png,.gif.

See Loading and Displaying Images for more complex example of working with uploaded files.



4.5.2.1.9. Filter

The Filter is a versatile tool for filtering lists of entities extracted from a database to display in a tabular form. The component enables quick data filtering by arbitrary conditions, as well as creating filters for repeated use.

Filter should be connected to the collectionDatasource containing a JPQL query. Its logic is based on the modification of this query in accordance with criteria provided by the user. Thus, filtering is done at the database level when the SQL query is executed, and only selected data is loaded to the Middleware and Client tiers.

Using a Filter

A typical filter is shown below:

gui filter descr

By default, the component is in quick filter mode. This means that a user can add a set of conditions for a one-off data search. After the screen is closed, the conditions will disappear.

To create a quick filter, click Add search condition link. The condition selection screen will be displayed:

gui filter conditions

Possible condition types are described below:

  • Attributes – attributes of this entity and related entities. Only persistent attributes are displayed. They should also either be explicitly set in the property element of the filter XML descriptor, or comply with the rules specified in the properties element.

  • Custom conditions – conditions specified by developer in the custom elements of the filter XML descriptor.

  • Create new…​ – enables creating a new arbitrary JPQL condition. This option is only available to users having the specific cuba.gui.filter.customConditions permission.

Selected conditions are displayed at the top of the filter panel. The gui_filter_remove_condition icon will appear next to each condition field, allowing them to be removed from the set.

Quick filters can be saved for further re-use. In order to save a quick filter, click the filter settings icon, select Save/Save as and provide a new filter name in the popup dialog:

gui filter name

After that, the filter will be saved and will appear in the drop-down menu of the Search button.

gui filter reset

The filter settings popup button provides the list of options for filter management:

  • Save – save changes to the current filter.

  • Save as – save the filter under a new name.

  • Edit – open the filter editor (see below).

  • Make default – make the filter default for this screen. The filter will be automatically displayed on the filter panel when the screen is opened.

  • Remove – delete the current filter.

  • Pin applied – use the results of the last search for sequential data filtering (see Applying Filters Sequentially).

  • Save as search folder – create a search folder based on the current filter.

  • Save as application folder – create an application folder based on the current filter. This option is available to users having the specific cuba.gui.appFolder.global permission only.

The Edit option opens the filter editor, allowing advanced configuration of the current filter:

gui filter editor

Filter name should be provided in the Name field. This name will be displayed in available filters list for the current screen.

Filter can be made global (i.e., available for all users) using the Available to all users checkbox, or default using the Default checkbox.

The filter conditions are contained in the tree. They can be added using the Add button, swapped using gui_filter_cond_down/gui_filter_cond_up or removed using the Remove button.

AND or OR grouping conditions can be added with the help of the corresponding buttons. All top level conditions (i.e., without explicit grouping) are joined with AND.

Selecting a condition in the tree opens the list of its properties in the right part of the editor.

The conditions can be made hidden or required by means of corresponding checkboxes. The hidden condition parameter is invisible to the user, so it should be provided when the filter is being edited.

Width property enables selecting the width of the parameter field on the filter panel for the current condition. By default, conditions on the filter panel are displayed in three columns. The field width equals to the number of columns it will occupy (1, 2 or 3).

Default parameter value for the current condition can be selected in the Default value field.

A custom caption for filter condition can be provided in the Caption field.

Operation field enables selecting the condition operator. The list of available operators depends on the attribute type.

Filter Component

XML name of the component: filter.

gui filter dia

The component is implemented for Web Client and Desktop Client.

An example of component declaration in XML screen descriptor is shown below:

<dsContext>
    <collectionDatasource id="carsDs" class="com.company.sample.entity.Car" view="carBrowse">
        <query>
            select c from ref$Car c order by c.createTs
        </query>
    </collectionDatasource>
</dsContext>
<layout>
    <filter id="carsFilter" datasource="carsDs">
        <properties include=".*"/>
    </filter>
    <table id="carsTable" width="100%">
        <columns>
            <column id="vin"/>
            <column id="model.name"/>
            <column id="colour.name"/>
        </columns>
        <rows datasource="carsDs"/>
    </table>
</layout>

In the example above, a collectionDatasource is defined in the dsContext. The datasource selects Car entity instances using JPQL query. The datasource which is to be filtered is specified in the filter component’s datasource attribute. Data is displayed using the Table component, which is connected to the same data source.

filter may contain nested elements. They describe conditions available for user selection in Add Condition dialog:

  • properties – multiple entity attributes can be made available for selection. This element has the following attributes:

    • include – required attribute. It contains a regular expression, which should match an entity attribute name.

    • exclude – contains a regular expression. If an attribute matches the expression, it will be excluded from previously included (using include).

    Example:

    <filter id="transactionsFilter" datasource="transactionsDs">
        <properties include=".*" exclude="(masterTransaction)|(authCode)"/>
    </filter>

    The following entity attributes are ignored when properties element is used:

    • Not accessible due to security permissions.

    • Collections (@OneToMany, @ManyToMany).

    • Non-persistent attributes.

    • Attributes that do not have localized names.

    • Attributes annotated with @SystemLevel.

    • Attributes of type byte[].

    • The version attribute.

  • property – explicitly includes an entity attribute by name. This element has the following attributes:

    • name – required attribute, containing the name of entity attribute to be included. It can be a path (using ".") in the entity graph. For example:

      <filter id="transactionsFilter" datasource="transactionDs" applyTo="table">
          <properties include=".*" exclude="(masterTransaction)|(authCode)"/>
          <property name="creditCard.maskedPan" caption="msg://EmbeddedCreditCard.maskedPan"/>
          <property name="creditCard.startDate" caption="msg://EmbeddedCreditCard.startDate"/>
      </filter>
    • caption – localized entity attribute name displayed in filter conditions. Generally it is a string with the msg:// prefix in accordance with MessageTools.loadString() rules.

      If the name attribute is specified as an entity graph path (using ".") , the caption attribute is required.

    • paramWhere − specifies the JPQL expression which is used to select the list of condition parameter values if the parameter is a related entity. The {E} placeholder should be used in the expression instead of the alias of the entity being selected.

      For example, let us assume that Car has a reference to Model. Then possible condition parameter values list can be limited to Audi models only:

      <filter id="carsFilter" datasource="carsDs">
          <property name="model" paramWhere="{E}.manufacturer = 'Audi'"/>
      </filter>

      Screen parameters, session attributes and screen components including those showing other parameters can be used in JPQL expression. Query parameters specification rules are described in CollectionDatasourceImpl Queries.

      An example of session and screen parameters usage is shown below:

      {E}.createdBy = :session$userLogin and {E}.name like :param$groupName

      With the paramWhere clause, you can introduce dependencies between parameters. For example, let us assume that Manufacturer is a separate entity. That is Car has a reference to Model which in turn has a reference to Manufacturer. Then you may want to create two conditions for the Cars filter: first to select a Manufacturer and second to select a Model. To restrict the list of models by previously selected manufacturer, add a parameter to the paramWhere expression:

      {E}.manufacturer.id = :component$filter.model_manufacturer90062

      The parameter references a component which displays Manufacturer parameter. You can see the name of the component showing condition parameter by opening context menu on a condition table row in the filter editor:

      gui filter component name
    • paramView − specifies a view, which will be used to load the list of condition parameter values if the parameter is a related entity. For example, _local. If view is not specified, _minimal view will be used.

  • custom is an element defining an arbitrary condition. The element content should be a JPQL expression (JPQL Macros can be used), which will be added to the data source query’s where clause. The {E} placeholder should be used in the expression instead of the alias of the entity being selected. The condition can only have one parameter denoted by "?" if used.

    A value of custom condition can contain special characters, for example "%" or "_" for "like" operator. If you want to escape these characters, add escape '<char>' to your condition, for example:

    {E}.name like ? escape '\'

    Then if you use foo\% as a value of the condition parameter, the search will interpret "%" as a character in your name and not as a special character.

    An example of a filter with arbitrary conditions is shown below:

    <filter id="carsFilter" datasource="carsDs">
        <properties include=".*"/>
        <custom name="vin" paramClass="java.lang.String" caption="msg://vin">
          {E}.vin like ?
        </custom>
        <custom name="colour" paramClass="com.company.sample.entity.Colour" caption="msg://colour"
                inExpr="true">
          ({E}.colour.id in (?))
        </custom>
        <custom name="repair" paramClass="java.lang.String" caption="msg://repair"
                join="join {E}.repairs cr">
          cr.description like ?
        </custom>
        <custom name="updateTs" caption="msg://updateTs">
          @between({E}.updateTs, now-1, now+1, day)
        </custom>
    </filter>

    custom conditions are displayed in the Custom conditions section of the Add condition dialog:

    gui filter custom

    Attributes of custom:

    • name − required attribute, condition name.

    • caption − required attribute, localized condition name. Generally it is a string with the msg:// prefix in accordance with MessageTools.loadString() rules.

    • paramClass − Java class of the condition parameter. If the parameter is not specified, this attribute is optional.

    • inExpr − should be set to true, if the JPQL expression contains in (?) conditions. In this case user will be able to enter several condition parameter values.

    • join − optional attribute. It specifies a string, which will be added to the data source query from section. This can be required to create a complex condition based on an attribute of a related collection. join or left join statements should be included into the attribute value.

      For example, let us assume that the Car entity has a repairs attribute, which is a related entity Repair instances collection. Then the following condition can be created to filter Car by Repair entity’s description attribute:

      <filter id="carsFilter" datasource="carsDs">
          <custom name="repair"
                  caption="msg://repair"
                  paramClass="java.lang.String"
                  join="join {E}.repairs cr">
              cr.description like ?
          </custom>
      </filter>

      If the condition above is used, the original data source query

      select c from sample$Car c order by c.createTs

      will be transformed into the following one:

      select c from sample$Car c join c.repairs cr
      where (cr.description like ?)
      order by c.createTs
    • paramWhere − specifies a JPQL expression used to select the list of condition parameter values if the parameter is a related entity. See the description of the property element’s attribute of the same name.

    • paramView − specifies a view, which will be used when a list of condition parameter values are loaded if the parameter is a related entity. See the description of the property element’s attribute of the same name.

filter attributes:

  • editable – if the attribute value is false, the Edit option is disabled.

  • manualApplyRequired − defines when the filter will be applied. If the attribute value is false, the filter (default or empty) will be applied when the screen is opened. It means that the datasource will be refreshed and linked components (e.g. Table) will display data. If the value is true, the filter will be applied only after the Search button is clicked.

    This attribute takes precedence over the cuba.gui.genericFilterManualApplyRequired application property.

  • useMaxResults − limits the page size of entity instances loaded into the data source. It is set to true by default.

    If the attribute value is false, the filter will not show the Show rows field. The number of records in the data source (and displayed in the table accordingly) will be limited only by the MaxFetchUI parameter of the entity statistics, which is set to 10000 by default.

    If the attribute is not specified or is true, the Show rows field will be displayed only if the user has specific cuba.gui.filter.maxResults permission. If the cuba.gui.filter.maxResults permission is not granted, the filter will force selecting only the first N rows without user to be able to disable it or specify another N. N is defined by FetchUI, DefaultFetchUI parameters. They are obtained from the entity statistics mechanism.

    A filter shown below has the following parameters: useMaxResults="true", the cuba.gui.filter.maxResults permission is denied, and cuba.gui.filter.maxResults DefaultFetchUI = 2.

gui filter useMaxRezult
  • textMaxResults - enables using the text field instead of the drop-down list as the Show rows field. false by default.

  • folderActionsEnabled − if it is set to false, the following filter actions will be hidden: Save as Search Folder, Save as Application Folder. By default, the attribute value is true, and Save as Search Folder, Save as Application Folder are available.

  • applyTo − optional attribute, contains the identifier of a component associated with the filter. It is used when access to related component presentations is required. For example, when saving the filter as a search folder or as an application folder, the presentation that will be applied when browsing this folder can be specified.

    gui filter apply to
  • caption - enables setting a custom caption for the filter panel.

  • columnsQty - defines the number of columns for conditions on the filter panel. Default value is 3.

  • defaultMode - defines the filter default mode. Possible values are generic and fts. When fts value is set then the filter will be opened in the full text search mode (if the entity is indexed). The default value is generic.

  • modeSwitchVisible - defines the visibility of the checkbox that switches the filter to the full text search mode. If full text search is unavailable then the checkbox will be invisible despite of the defined value. Possible values are true and false (true by default).


Attributes of filter

applyTo - caption - columnsQty - datasource - defaultMode - editable - enable - folderActionsEnabled - id - manualApplyRequired - margin - modeSwitchVisible stylename - textMaxResults - useMaxResults - visible - width

Elements of filter

custom - properties - property

Attributes of properties

exclude - include

Attributes of property

caption - name - paramView - paramWhere

Attributes of custom

caption - name - inExpr - join - paramClass - paramView - paramWhere


User Permissions

  • To create/change/delete global (available to all users) filters, user must have the cuba.gui.filter.global permission.

  • To create/change custom conditions user must have a cuba.gui.filter.customConditions permission.

  • To change the maximum number of rows per table page using the Show rows field, user must have the cuba.gui.filter.maxResults permission. See also the useMaxResults filter attribute.

For specific permissions configuration information, see Security Subsystem.

External Filter Control Parameters

  • Screen invocation parameters

    It is possible to specify a filter and its parameters which should be applied when the screen is opened. For this purpose, the filter should be created in advance, stored in the database, and a corresponding record in the SEC_FILTER table should have a value in the CODE field.

    To specify a filter code, pass to the screen a parameter with the same name as filter component identifier in this screen. Parameter value should be the code of the filter.

    To set filter parameter values, pass to the screen parameters with the names equal to parameter names and their values in string format.

    An example of main menu item descriptor is shown below. It sets a filter with the FilterByVIN code to the carsFilter component of the sample$Car.browse screen which it opens. It also sets TMA value to the component$carsFilter.vin79216 condition:

    <item id="sample$Car.browse">
        <param name="carsFilter" value="FilterByVIN"/>
        <param name="component$carsFilter.vin79216" value="TMA"/>
    </item>

    It should be noted that a filter with a defined CODE field has some specifics:

    • It cannot be edited by users.

    • Name of this filter can be displayed in several languages. To achieve this, specify a string with key equal to the filter code in the application main message pack.

Applying Filters Sequentially

If the cuba.allowQueryFromSelected application property is enabled, the last applied filter and the current filtered results can be pinned via the component’s user interface. After that another filter or other parameters of the current filter can be selected and applied to the currently selected records.

This approach helps to achieve two aims:

  • Decompose complex filters, which may lead to better performance as well.

  • Apply filters to the records selected using application or search folders.

Take the following steps to use sequential filters. First, choose and apply one of the filters. Next click the filter settings button and select Pin applied. The filter will be pinned at the top of the filter panel. Then another filter can be applied to the selected records and so on. Any number of filters can be applied sequentially. Filters can also be removed using gui_filter_remove button.

gui filter sequential

The sequential filters implementation is based on the ability of DataManager to run sequential queries.

API for Working with Filter Parameters

The Filter interface provides methods for reading and writing of filter parameter values in a screen controller:

  • setParamValue(String paramName, Object value)

  • getParamValue(String paramName)

paramName - filter parameter name. Parameter name is a part of component name that displays a parameter value. The procedure of getting a component name was described above. Parameter name is placed after the last dot in a component name. For example, if the component name is component$filter.model_manufacturer90062, then the parameter name is model_manufacturer90062.

Note that you cannot use these methods in the init() method of the screen controller, because the filter is not initialized at that moment. A good place to work with filter parameters is the ready() method.

Full-Text Search Mode in Filter

If a filter datasource contains entities that are indexed by the full-text search subsystem (see CUBA Platform. Full Text Search), then a full-text search mode is available in the filter. Use the Full-Text Search checkbox to switch to this mode.

gui filter fts

In the full-text search mode, the filter contains only one text field for search criteria, and the search is performed in entity fields indexed by the FTS subsystem.

If a table is defined in the applyTo attribute, then placing the mouse cursor on the table row will display a tooltip with the information what entity attributes satisfy the search criteria.

For hiding the filter mode checkbox, set false value to the modeSwitchVisible filter attribute.

If you want the filter to be opened in the full-text search mode by default, set fts value to the defaultMode filter attribute.

4.5.2.1.10. GroupTable

GroupTable component is a table with an ability to group information dynamically by any field. In order to group a table by a column the required column should be dragged to the left and dropped on the gui_groupTableIcon element of the table header. Grouped values can be expanded and collapsed using gui_groupBox_plus/gui_groupBox_minus buttons.

gui groupTableDragColumn

XML name of the component: groupTable.

gui GroupTable dia

Component is implemented only for Web Client. In Desktop Client it behaves like a regular table.

groupDatasource must be specified for GroupTable in the datasource attribute of the rows element. Otherwise, grouping will not work. Example:

<dsContext>
    <groupDatasource id="ordersDs" class="com.sample.sales.entity.Order"
                     view="orderWithCustomer">
        <query>
            select o from sales$Order o order by o.date
        </query>
    </groupDatasource>
</dsContext>
<layout>
    <groupTable id="ordersTable" width="100%">
        <columns>
            <group>
                <column id="date"/>
            </group>
            <column id="customer.name"/>
            <column id="amount"/>
        </columns>
        <rows datasource="ordersDs"/>
    </groupTable>

group is an optional element that can be present in a single instance inside columns. It contains a set of column elements, by which grouping will be performed initially when opening a screen.

Each column element can contain the groupAllowed attribute with boolean value. This attribute controls whether a user can group by this column.

If aggregatable attribute is true, the table shows aggregation results for each group and results for all rows in an additional row on the top. If showTotalAggregation attribute is false, results for all rows are not shown.

The rest of the GroupTable functionality is similar to a simple Table.



4.5.2.1.11. Label

Label is a text component that displays static text or value of an entity attribute.

XML name of the component: label

gui label dia

The Label component is implemented for Web Client and Desktop Client.

Below is an example of setting a label with text taken from the localized message pack:

<label value="msg://orders"/>

The value attribute sets text for a label.

In a web client, the text contained in value will be split into multiple lines if its length exceeds the width value. Therefore, to display a multiline label, it is sufficient to specify an absolute value of width. If the label text is too long and the value of width is not specified, the text will be truncated.

<label value="Label, which should be split into multiple lines"
       width="200px"/>

You can set label parameters in the screen controller. To do this, you should specify a component identifier and get a reference to the component in the controller:

<label id="dynamicLabel"/>
@Inject
private Label dynamicLabel;

public void init(Map<String, Object> params) {
    dynamicLabel.setValue("Some value");
}

The Label component can display value of an entity attribute. For this purpose, datasource and property attributes are used. For example:

<dsContext>
    <datasource id="customerDs" class="com.sample.sales.entity.Customer" view="_local"/>
</dsContext>
<layout>
    <label datasource="customerDs" property="name"/>

In the example above, component displays the name attribute of the Customer entity located in the customerDs data source.

htmlEnabled attribute indicates the way the value attribute will be interpreted: if htmlEnabled="true", the attribute will be treated as HTML code, otherwise as a plain string. Note that the desktop implementation of the screen does not support all HTML tags.


Attributes of label

align - datasource - enable - height - htmlEnabled - icon - id - property - stylename - value - visible - width

Elements of label

formatter


Link is a hyperlink, which enables uniform opening of external web resources for the Web and Desktop client.

XML-name of the component: link

gui link dia

An example of XML-description for link:

<link caption="Link" url="https://www.cuba-platform.com" target="_blank"/>

link attributes:


Attributes of link

align - caption - description - enable - icon - id - stylename - url - target - visible - width


4.5.2.1.13. LinkButton

LinkButton is a button that looks like a hyperlink.

XML name of the component: linkButton

gui linkButton dia

The link button component is implemented for Web Client and Desktop Client.

The link button can contain text or icon (or both). The figure below shows different types of buttons.

gui linkButtonTypes

The link button differs from regular Button only in its appearance. All properties and behavior are identical to those described for Button.

Below is an example of XML description of a link button that invokes the someMethod() method of a controller. The link button has a caption (the caption attribute), a tooltip (the description attribute) and an icon (the icon attribute):

<linkButton id="linkButton"
            caption="msg://linkButton"
            description="Press me"
            icon="icons/save.png"
            invoke="someMethod"/>

Attributes of linkButton

action - align - caption - description - enable - icon - id - invoke - stylename - visible - width


4.5.2.1.14. LookupField

This is a component to select a value from drop-down list. Drop-down list provides the filtering of values as the user inputs some text, and the pagination of available values.

gui lookupField

XML name of the component: lookupField.

gui LookupField dia

The LookupField component is implemented for Web Client and Desktop Client.

  • The simplest case of using LookupField is to select an enumeration value for an entity attribute. For example, a Role entity has a type attribute of the RoleType type, which is an enumeration. Then you can use LookupField to edit this attribute as follows:

    <dsContext>
        <datasource id="roleDs" class="com.haulmont.cuba.security.entity.Role" view="_local"/>
    </dsContext>
    <layout>
        <lookupField datasource="roleDs" property="type"/>

    In the example above, the screen defines roleDs data source for the Role entity. In the lookupField component, a link to a data source is specified in the datasource attribute, and a name of an entity attribute is specified in the property attribute. In this case, the entity attribute is an enumeration and the drop-down list will display localized names of all enumeration values.

  • Similarly, LookupField can be used to select an instance of a related entity. optionsDatasource attribute is used to create a list of options:

    <dsContext>
        <datasource id="carDs" class="com.company.sample.entity.Car" view="_local"/>
        <collectionDatasource id="coloursDs" class="com.company.sample.entity.Colour" view="_local">
            <query>select c from sample$Colour c</query>
        </collectionDatasource>
    </dsContext>
    <layout>
        <lookupField datasource="carDs" property="colour" optionsDatasource="coloursDs"/>

    In this case, the component will display instance names of the Colour entity located in the colorsDs data source, and the selected value will be set into the colour attribute of the Car entity, which is located in the carDs data source.

    captionProperty attribute defines which entity attribute can be used instead of an instance name for string option names.

  • The list of component options can be specified arbitrarily using the setOptionsList(), setOptionsMap() and setOptionsEnum() methods, or using the XML optionsDatasource attribute.

    • setOptionsList() allows you to programmatically specify a list of component options. To do this, declare a component in the XML descriptor:

      <lookupField id="numberOfSeatsField" datasource="modelDs" property="numberOfSeats"/>

      Then inject the component into the controller and specify a list of options in the init() method:

      @Inject
      protected LookupField numberOfSeatsField;
      
      @Override
      public void init(Map<String, Object> params) {
          List<Integer> list = new ArrayList<>();
          list.add(2);
          list.add(4);
          list.add(5);
          list.add(7);
          numberOfSeatsField.setOptionsList(list);
      }

      In the component’s drop-down list the values 2, 4, 5 and 7 will be displayed. Selected number will be put into the numberOfSeats attribute of an entity located in the modelDs data source.

    • setOptionsMap() allows you to specify string names and option values separately. For example, in the numberOfSeatsField component in the XML descriptor, specify an option map in init():

      @Inject
      protected LookupField numberOfSeatsField;
      
      @Override
      public void init(Map<String, Object> params) {
          Map<String, Object> map = new LinkedHashMap<>();
          map.put("two", 2);
          map.put("four", 4);
          map.put("five", 5);
          map.put("seven", 7);
          numberOfSeatsField.setOptionsMap(map);
      }

      In the component’s drop-down list, two, four, five, seven strings will be displayed. However, the value of the component will be a number that corresponds to the selected option. It will be put into the numberOfSeats attribute of an entity located in the modelDs data source.

    • setOptionsEnum() takes the class of an enumeration as a parameter. The drop-down list will show localized names of enum values, the value of the component will be an enum value.

  • Using the filterMode attribute, option filtering type can be defined for the user input:

    • NO − no filtering.

    • STARTS_WITH − by the beginning of a phrase.

    • CONTAINS − by any occurrence (is used by default).

  • If the LookupField component is not required and if the related entity attribute is not declared as required, the list of component options has an empty row. If this row is selected, the component returns null. The nullName attribute allows you to specify a row to be displayed in this case instead of an empty one. Below is an example:

    <lookupField datasource="carDs" property="colour" optionsDatasource="coloursDs" nullName="(none)"/>

    In this case, instead of an empty row, (none) will be displayed. If this row is selected, null will be set to a related entity attribute.

    If you specify a list of options programmatically using setOptionsList(), you can pass one of the options into setNullOption() method. Then, if the user selects it, the component value will be null.

  • The LookupField component is able to handle user input if there is no suitable option in the list. In this case, setNewOptionAllowed() and setNewOptionHandler() are used. For example:

    @Inject
    protected LookupField colourField;
    
    @Inject
    protected CollectionDatasource<Colour, UUID> coloursDs;
    
    @Override
    public void init(Map<String, Object> params) {
        colourField.setNewOptionAllowed(true);
        colourField.setNewOptionHandler(new LookupField.NewOptionHandler() {
            @Override
            public void addNewOption(String caption) {
                Colour colour = new Colour();
                colour.setName(caption);
                coloursDs.addItem(colour);
                colourField.setValue(colour);
            }
        });
    }

    The NewOptionHandler handler is invoked if the user enters a value that does not coincide with any option and presses Enter. In this case, a new Colour entity instance is created in the handler, its name attribute is set to the value entered by the user, this instance is added to the options data source and selected in the component.

    Instead of implementing the LookupField.NewOptionHandler interface for processing user input, the controller method name can be specified in the newOptionHandler XML-attribute. This method should have two parameters, one of LookupField type, and the other of String type. They will be set to the component instance and the value entered by the user, accordingly. The newOptionAllowed attribute is used to enable adding new options instead of the setNewOptionAllowed() method.

  • The textInputAllowed XML attribute can be used to disable filtering options from keyboard. It can be convenient for short lists. The default value is true.

  • The pageLength XML attribute allows you to redefine the number of options on one page of the drop-down list, specified in the cuba.gui.lookupFieldPageLength application property.



4.5.2.1.15. LookupPickerField

The LookupPickerField component enables to display an entity instance in a text field, select an instance in a drop-down list and perform actions by pressing buttons on the right.

gui lookupPickerField

XML name of the component: lookupPickerField.

gui LookupPickerField dia

The component is implemented for Web Client and Desktop Client.

In fact, LookupPickerField is a hybrid of LookupField and PickerField. Thus it has the same features except the default list of actions added when determining the component in XML: for LookupPickerField these are lookup lookupBtn and open openBtn actions.

Below is an example of using LookupPickerField to select a value of the colour reference attribute of the Car entity:

<dsContext>
    <datasource id="carDs" class="com.company.sample.entity.Car" view="_local"/>
    <collectionDatasource id="coloursDs" class="com.company.sample.entity.Colour" view="_local">
        <query>select c from sample$Colour c</query>
    </collectionDatasource>
</dsContext>
<layout>
    <lookupPickerField datasource="carDs" property="colour" optionsDatasource="coloursDs"/>


4.5.2.1.16. MaskedField

This is a text field, in which data is entered in a predefined format. For example, it is convenient to use MaskedField to enter telephone numbers.

XML name of the component: maskedField.

gui MaskedField dia

The MaskedField component is implemented for Web Client only.

Basically, MaskedField repeats the functionality of TextField, except that you cannot set datatype for it. So, MaskedField is intended for work only with text and entity attributes of type String. MaskedField has the following specific attributes:

  • mask – sets a mask for the field. To set a mask, use the following characters:

    • # – number

    • U – uppercase letter

    • L – lowercase letter

    • ? – letter

    • А – letter or number

    • * – any character

    • H – uppercase hex character

    • H – lowercase hex character

    • ~ – " +" or "-" character

  • valueMode – defines a format of a returned value (with a mask or not) and can take either masked or clear.

Example of a text field with a mask for entering telephone numbers is provided below:

<maskedField id="phoneNumberField" mask="(###)###-##-##" valueMode="masked"/>
<button caption="msg://showPhoneNumberBtn" invoke="showPhoneNumber"/>
@Inject
private MaskedField phoneNumberField;

public void showPhoneNumber(){
    showNotification((String) phoneNumberField.getValue(), NotificationType.HUMANIZED);
}
gui MaskedField
gui MaskedField maskedValueMode

Attributes of maskedField

align - caption - datasource - description - editable - enable - height - icon - id - mask - maxLength - property - required - requiredMessage - stylename - trim - valueMode - visible - width

Elements maskedField

validator


4.5.2.1.17. OptionsGroup

This is a component that allows a user to choose from a list of options. Radio buttons are used to select a single value; a group of checkboxes is used to select multiple values.

gui optionsGroup

XML name of the component: optionsGroup.

gui OptionsGroup dia

The OptionsGroup component is implemented for Web Client and Desktop Client.

  • The simplest case of using OptionsGroup is to select an enumeration value for an entity attribute. For example, a Role entity has type attribute of the RoleType type, which is an enumeration. Then you can use OptionsGroup to edit this attribute as follows:

    <dsContext>
        <datasource id="roleDs" class="com.haulmont.cuba.security.entity.Role" view="_local"/>
    </dsContext>
    <layout>
        <optionsGroup datasource="roleDs" property="type"/>

    In the example above roleDs data source is defined for the Role entity. In the optionsGroup component, the link to the data source is specified in the datasource attribute and the name of the entity attribute is set in the property attribute.

    As a result, the component will be as follows:

gui optionsGroup roleType
  • The list of component options can be specified arbitrarily using the setOptionsList(), setOptionsMap() and setOptionsEnum() methods, or using an optionsDatasource attribute.

    • setOptionsList() allows you to specify programmatically a list of component options. To do this, declare a component in the XML descriptor:

      <optionsGroup id="numberOfSeatsField"/>

      Then inject the component into the controller and specify a list of options in the init() method:

      @Inject
      protected OptionsGroup numberOfSeatsField;
      
      @Override
      public void init(Map<String, Object> params) {
          List<Integer> list = new ArrayList<>();
          list.add(2);
          list.add(4);
          list.add(5);
          list.add(7);
          numberOfSeatsField.setOptionsList(list);
      }

      The component will be as follows:

      gui optionsGroup integerList

      Depending on the selected option, the getValue() method of the component will return Integer values: 2, 4, 5, 7.

    • setOptionsMap() allows you to specify string names and option values separately. For example, we can set the following options map for the numberOfSeatsField component, described the XML descriptor, in the init() method of the controller:

      @Inject
      protected OptionsGroup numberOfSeatsField;
      
      @Override
      public void init(Map<String, Object> params) {
          Map<String, Object> map = new LinkedHashMap<>();
          map.put("two", 2);
          map.put("four", 4);
          map.put("five", 5);
          map.put("seven", 7);
          numberOfSeatsField.setOptionsMap(map);
      }

      The component will be as follows:

      gui optionsGroup integerMap

      Depending on the selected option, the getValue() method of the component will return Integer values: 2, 4, 5, 7, and not the strings that are displayed on the screen.

    • setOptionsEnum() takes a class of enumeration as a parameter. The options list will consist of localized names of enum values, the value of the component will be an enum value.

    • The component can take a list of options from a data source. For this purpose, the optionsDatasource attribute is used. For example:

      <dsContext>
          <collectionDatasource id="coloursDs" class="com.company.sample.entity.Colour" view="_local">
              <query>select c from sample$Colour c</query>
          </collectionDatasource>
      </dsContext>
      <layout>
          <optionsGroup id="coloursField" optionsDatasource="coloursDs"/>

      In this case, the coloursField component will display instance names of the Colour entity, located in the coloursDs data source, and its getValue() method will return the selected entity instance.

      With the help of captionProperty attribute entity attribute to be used instead of an instance name for string option names can be defined.

  • multiselect attribute is used to switch OptionsGroup to a multiple choice mode. If multiselect is turned on, the component is displayed as a group of independent checkboxes, and the component value is a list of selected options.

    For example, if we create the component in the XML screen descriptor:

    <optionsGroup id="roleTypesField" multiselect="true"/>

    and set a list of options for it – RoleType enumeration values:

    @Inject
    protected OptionsGroup roleTypesField;
    
    @Override
    public void init(Map<String, Object> params) {
        roleTypesField.setOptionsList(Arrays.asList(RoleType.values()));
    }

    then the component will be as follows:

    gui optionsGroup roleType multi

    In this case the getValue() method of the component will return a java.util.List, containing RoleType.READONLY and RoleType.DENYING values.

    The example above also illustrates an ability of the OptionsGroup component to display localized values of enumerations included in the data model.

  • The orientation attribute defines the orientation of group elements. By default, elements are arranged vertically. The horizontal value sets the horizontal orientation.



4.5.2.1.18. PasswordField

This is a text field that displays echo characters instead of those entered by a user.

XML name of the component: passwordField.

gui PasswordField dia

PasswordField is implemented for Web Client and Desktop Client.

Basically, PasswordField is similar to TextField apart from the ability to set datatype. PasswordField is intended to work with text and entity attributes of type String only.

Example:

<passwordField id="passwordField" caption="msg://name"/>
<button caption="msg://buttonsName" invoke="showPassword"/>
@Inject
private PasswordField passwordField;

public void showPassword(){
    showNotification((String) passwordField.getValue(), NotificationType.HUMANIZED);
}
gui passwordField

The autocomplete attribute allows you to enable saving passwords in the web browser. It is disabled by default.


Attributes of passwordField

align - autocomplete - caption - datasource - description - editable - enable - height - icon - id - maxLength - property - required - requiredMessage - stylename - visible - width

Elements of passwordField

validator


4.5.2.1.19. PickerField

PickerField displays an entity instance in a text field and performs actions when a user clicks buttons on the right.

PickerField

XML name of the component: pickerField.

gui pickerField dia

The PickerField component is implemented for Web Client and Desktop Client.

  • As a rule, PickerField is used for working with reference entity attributes. It is sufficient to specify datasource and property attributes for the component:

    <dsContext>
        <datasource id="carDs" class="com.company.sample.entity.Car" view="_local"/>
    </dsContext>
    <layout>
        <pickerField datasource="carDs" property="colour"/>

    In the example above, the screen defines carDs data source for a Car entity having the colour attribute. In the pickerField element, a link to a data source is specified in the datasource attribute, and a name of an entity attribute is set in the property attribute. The entity attribute should refer to another entity, in the example above it is Colour.

  • For PickerField, you can define an arbitrary number of actions, displayed as buttons on the right. It can be done either in the XML descriptor using the actions nested element, or programmatically in the controller using addAction().

    • There are standard actions, defined by the PickerField.ActionType: lookup, clear, open. They perform the selection of a related entity, clearing the field and opening the edit screen for a selected related entity, respectively. When declaring standard actions in XML, you do not have to define any attributes except the identifier. If no actions in the actions element are defined when declaring the component, the XML loader will define lookup and clear actions for it. To add a default action, for example, open, you need to define the actions element as follows:

      <pickerField datasource="carDs" property="colour">
          <actions>
              <action id="lookup"/>
              <action id="open"/>
              <action id="clear"/>
          </actions>
      </pickerField>

      The action element does not extend but overrides a set of standard actions. Identifiers of all required actions have to be defined explicitly. The component looks as follows:

      gui pickerFieldActionsSt

      Use addLookupAction(), addOpenAction() and addClearAction() to set standard actions programmatically. If the component is defined in the XML descriptor without actions nested element, it is sufficient to add missing actions:

      @Inject
      protected PickerField colourField;
      
      @Override
      public void init(Map<String, Object> params) {
          colourField.addOpenAction();
      }

      If the component is created in the controller, it will get no default actions and you need to explicitly add all necessary actions:

      @Inject
      protected ComponentsFactory componentsFactory;
      
      @Override
      public void init(Map<String, Object> params) {
          PickerField colourField = componentsFactory.createComponent(PickerField.NAME);
          colourField.setDatasource(carDs, "colour");
          colourField.addLookupAction();
          colourField.addOpenAction();
          colourField.addClearAction();
      }

      You can parameterize standard actions. The XML descriptor has limited abilities to do this: there is only openType attribute, in which you can specify the mode to open a selection screen (for LookupAction) or edit screen (for OpenAction).

      If you create actions programmatically, you can specify any properties of PickerField.LookupAction, PickerField.OpenAction and PickerField.ClearAction objects returned by methods of adding standard actions. For example, you can set a specific lookup screen as follows:

      PickerField.LookupAction lookupAction = customerField.addLookupAction();
      lookupAction.setLookupScreen("customerLookupScreen");

      For more information, see JavaDocs for standard actions classes.

    • Arbitrary actions in the XML descriptor are also defined in the actions nested element, for example:

      <pickerField datasource="carDs" property="colour">
          <actions>
              <action id="lookup"/>
              <action id="show" icon="icons/show.png"
                      invoke="showColour" caption=""/>
          </actions>
      </pickerField>

      You can programmatically set an arbitrary action as follows:

      @Inject
      protected PickerField colourField;
      
      @Override
      public void init(Map<String, Object> params) {
          colourField.addAction(new AbstractAction("show") {
              @Override
              public void actionPerform(Component component) {
                  showColour(colourField.getValue());
              }
      
              @Override
              public String getCaption() {
                  return "";
              }
      
              @Override
              public String getIcon() {
                  return "icons/show.png";
              }
          });
      }

      The declarative and programmatic creation of actions is described in Actions. The Action Interface section.

  • PickerField can be used without binding to entities, i.e., without setting datasource and property. In this case, metaClass attribute should be used to specify an entity type for PickerField. For example:

    <pickerField id="colourField" metaClass="sample$Colour"/>

    You can get an instance of a selected entity by injecting the component into a controller and invoking its getValue() method.

    Warning

    For proper operation of the PickerField component you need either set a metaClass attribute, or simultaneously set datasource and property attributes.

  • You can use keyboard shortcuts in PickerField, see Keyboard Shortcuts for details.


Attributes of pickerField

align - caption - captionProperty - datasource - description - editable - enable - height - icon - id - metaClass - property - required - requiredMessage - stylename - visible - width

Elements of pickerField

actions - validator


4.5.2.1.20. PopupButton

This is a button with a drop-down list of actions.

PopupButton

XML name of the component: popupButton.

gui popupButton dia

The component is implemented for Web Client and Desktop Client.

PopupButton can contain text or icon (or both). The figure below shows different types of buttons.

gui popupButtonTypes

Below is an example of a button with a drop-down list containing two actions.

<popupButton id="popupButton" caption="msg://popupButton" description="Press me">
    <actions>
        <action id="popupAction1" caption="msg://action1" invoke="someAction1"/>
        <action id="popupAction2" caption="msg://action2" invoke="someAction2"/>
    </actions>
</popupButton>

The button has a caption, which is specified using the caption attribute, and a tooltip defined in the description attribute. The drop-down actions list is specified in the actions element. PopupButton displays only the following action properties: caption, enable, visible. The description and shortcut properties are ignored. Handling of the icon property depends on the cuba.gui.showIconsForPopupMenuActions application property and the showActionIcons attribute of the component. The latter has priority.


Attributes of popupButton

align - caption - description - enable - icon - id - showActionIcons - stylename - visible - width

Elements of popupButton

actions


4.5.2.1.21. ProgressBar

The ProgressBar component is designed to display the progress of a long process.

gui progressBar

XML name of the component: progressBar

gui progressBar dia

The component is implemented for Web Client and Desktop Client.

Below is an example of the component usage together with the background tasks mechanism:

<progressBar id="progressBar" width="100%"/>
@Inject
protected ProgressBar progressBar;

@Inject
protected BackgroundWorker backgroundWorker;

private static final int ITERATIONS = 5;

@Override
public void init(Map<String, Object> params) {
    BackgroundTask<Integer, Void> task = new BackgroundTask<Integer, Void>(300, this) {
        @Override
        public Void run(TaskLifeCycle<Integer> taskLifeCycle) throws Exception {
            for (int i = 1; i <= ITERATIONS; i++) {
                TimeUnit.SECONDS.sleep(2); // time consuming task
                taskLifeCycle.publish(i);
            }
            return null;
        }

        @Override
        public void progress(List<Integer> changes) {
            float lastValue = changes.get(changes.size() - 1);
            progressBar.setValue(lastValue / ITERATIONS);
        }
    };

    BackgroundTaskHandler taskHandler = backgroundWorker.handle(task);
    taskHandler.execute();
}

Here in the BackgroundTask.progress() method, which is executed in UI thread, the ProgressBar component is set to the current value. The component value should be a float number from 0.0 to 1.0.

If a running process is unable to send information about the progress, an indeterminate state of the indicator can be displayed. Set the indeterminate to true to show an indeterminate state. Default is false. For example:

<progressBar id="progressBar" width="100%" indeterminate="true"/>

By default indeterminate progress bar is displayed as horizontal bar. To make it a spinning wheel instead, set the attribute stylename="indeterminate-circle".


Attributes of progressBar

align - enable - height - id - indeterminate - stylename - visible - width


4.5.2.1.22. RelatedEntities

RelatedEntities component is a popup button with a drop-down list of classes related to the entity displayed in a table. Once the user selects the required entity class, a new browser screen is opened, containing the instances of this entity class, related to the entity instances selected in the initial table.

gui relatedEntities

The XML-name of the component: relatedEntities

gui relatedEntities dia

The component is implemented for Web Client and Desktop Client.

Related entities are selected considering the user permissions for entities, entity attributes and screens.

By default, the browser screen for the class selected in the drop-down is defined by convention ({entity_name}.browse,{entity_name}.lookup). Optionally, you can define the screen explicitly in the component.

A filter selecting records related to the selected entities is dynamically created in the browser window.

Example of using the component in screen XML-descriptor:

<table id="invoiceTable"
       multiselect="true"
       width="100%">
    <actions>
        <action id="create"/>
        <action id="edit"/>
        <action id="remove"/>
    </actions>

    <buttonsPanel id="buttonsPanel">
        <button id="createBtn"
                action="invoiceTable.create"/>
        <button id="editBtn"
                action="invoiceTable.edit"/>
        <button id="removeBtn"
                action="invoiceTable.remove"/>

        <relatedEntities for="invoiceTable"
                         openType="NEW_TAB">
            <property name="invoiceItems"
                      screen="sales$InvoiceItem.lookup"
                      filterCaption="msg://invoiceItems"/>
        </relatedEntities>
    </buttonsPanel>

The for attribute is required. It contains the table identifier.

The openType="NEW_TAB" attribute sets the opening mode of the lookup windows to new tab. The entity browser is opened in the current tab by default.

The property element enables explicitly defining the related entity displayed in the drop-down.

property attributes:

  • name – the current entity attribute name, referencing the related entity.

  • screen – the identifier of the browser screen that should be opened.

  • filterCaption – the name of the dynamically generated filter.

The exclude attribute enables excluding some of the related entities from the drop-down list. The value of the property is a regular expression matching reference attributes to exclude.

gui relatedEntitiesTable

Attributes of relatedEntities

align - caption - description - enable - exclude - for - icon - id - openType - stylename - visible - width

Attributes of property

caption - filterCaption - name - screen


4.5.2.1.23. RichTextArea

This is a text area to display and enter formatted text.

XML name of the component: richTextArea

gui RichTextArea dia

RichTextArea is implemented only for Web Client.

Basically, RichTextArea mirrors the functionality of TextField, except that you cannot set datatype for it. So, RichTextArea is intended for work only with text and entity attributes of type String.

gui RichTextAreaInfo


4.5.2.1.24. SearchPickerField

The SearchPickerField component is used to search for entity instances according to the entered string. A user should enter a few characters and press Enter. If several matches have been found, all of them will be displayed in a drop-down list. If only one instance matches the search query, it immediately becomes a component value. SearchPickerField also enables performing actions by clicking on buttons on the right.

gui searchPickerFieldOverlap

XML name of the component: searchPickerField.

gui SearchPickerField dia

The component is implemented for Web Client and Desktop Client.

  • To use SearchPickerField component, you need to create collectionDatasource and specify a query, which contains corresponding search conditions. Condition must contain a parameter named custom$searchString. This parameter will be populated with a substring entered by the user. A data source with a search condition should be defined in the optionsDatasource attribute of the component. For example:

    <dsContext>
        <datasource id="carDs" class="com.company.sample.entity.Car" view="_local"/>
        <collectionDatasource id="coloursDs" class="com.company.sample.entity.Colour" view="_local">
            <query>
                select c from sample$Colour c
                where c.name like :(?i)custom$searchString
            </query>
        </collectionDatasource>
    </dsContext>
    <layout>
        <searchPickerField datasource="carDs" property="colour" optionsDatasource="coloursDs"/>

    In this case, the component will look for instances of Colour entity according to the occurrence of the substring in its name attribute. The (?i) prefix is used for case-insensitive search (see Case-Insensitive Search for a Substring). The selected value will be set in the colour attribute of the Car entity located in the carDs datasource.

  • Using the minSearchStringLength attribute the minimum number of characters, which the user should enter to search for values, can be defined.

  • In the screen controller, two component methods can be implemented that will be invoked:

    • If the number of entered characters is less than the value of minSearchStringLength attribute.

    • If the search of characters entered by the user has returned no results.

    Below is an example of implementing methods to display messages to the user:

    @Inject
    private SearchPickerField colourField;
    
    @Override
    public void init(Map<String, Object> params) {
        colourField.setSearchNotifications(new SearchField.SearchNotifications() {
            @Override
            public void notFoundSuggestions(String filterString) {
                showNotification("No colours found for search string: " + filterString,
                                 NotificationType.TRAY);
            }
    
            @Override
            public void needMinSearchStringLength(String filterString, int minSearchStringLength) {
                showNotification("Minimum length of search string is " + minSearchStringLength,
                                 NotificationType.TRAY);
            }
        });
    }
  • SearchPickerField implements LookupField and PickerField interfaces. Thus, it inherits the same functionality except the default list of actions added when defining the component in XML: for SearchPickerField these are lookup lookupBtn and open openBtn actions.



4.5.2.1.25. Table

The Table component presents information in a table view, sorts data, manages table columns and headers and invokes actions for selected rows.

gui table

XML-name of the component: table

gui Table dia

The component is implemented for both Web Client and Desktop Client.

An example of component definition in an XML-descriptor of a screen:

<dsContext>
    <collectionDatasource id="ordersDs"
                          class="com.sample.sales.entity.Order"
                          view="orderWithCustomer">
        <query>
            select o from sales$Order o order by o.date
        </query>
    </collectionDatasource>
</dsContext>
<layout>
    <table id="ordersTable" width="300px">
        <columns>
            <column id="date"/>
            <column id="customer.name"/>
            <column id="amount"/>
        </columns>
        <rows datasource="ordersDs"/>
    </table>

In the example the dsContext element defines collectionDatasource, which selects Order entities using JPQL query. The rows element defines the data source, while columns element defines which entity attributes are used as table columns.

table elements:

  • rows – a required element; its datasource attribute defines the data source to be used by the table.

    Each row can have an icon in an additional column on the left. Create an implementation of the ListComponent.IconProvider interface in the screen controller and set it for the table:

    @Inject
    private Table<Customer> table;
    
    @Override
    public void init(Map<String, Object> params) {
        table.setIconProvider(new ListComponent.IconProvider<Customer>() {
            @Nullable
            @Override
            public String getItemIcon(Customer entity) {
                CustomerGrade grade = entity.getGrade();
                switch (grade) {
                    case PREMIUM: return "icons/premium_grade.png";
                    case HIGH: return "icons/high_grade.png";
                    case MEDIUM: return "icons/medium_grade.png";
                    default: return null;
                }
            }
        });
    }
  • columns – a required element defining the set of columns for a table.

    Each column is described in a nested column element with the following attributes:

    • id − a mandatory attribute, contains the name of an entity attribute displayed in the column. Can be either an attribute of the entity from the data source or a linked entity – object graph traversal is indicated with a dot. For example:

      <columns>
          <column id="date"/>
          <column id="customer"/>
          <column id="customer.name"/>
          <column id="customer.address.country"/>
      </columns>
    • caption − an optional attribute containing the column caption. If not specified, a localized attribute name will be displayed.

    • collapsed − an optional attribute; hides the column by default when set to true. Users can control column’s visibility using the menu available via a gui_table_columnControl button in the top right part of the table when the table’s columnControlVisible attribute is not false. By default, collapsed is false.

    • width − an optional attribute controlling default column width. May contain only numeric values in pixels.

    • align − an optional attribute that sets text alignment of column cells. Possible values: LEFT, RIGHT, CENTER. Default is LEFT.

    • editable − an optional attribute allowing editing of the corresponding column in the table. In order for a column to be editable, the editable attribute of the entire table should be set to true as well.

    • sortable − an optional attribute to disable sorting of the column. Takes effect if the whole table has sortable attribute set to true (which is by default).

    • maxTextLength – an optional attribute allowing to limit the number of characters in a cell. If the difference between the actual and the maximum allowed number of characters does not exceed the 10 character threshold, the "extra" characters remain unhidden. To see the entire record, users need to click on its visible part. An example of a column with a 5 character limitation:

      gui table column maxTextLength
    • linkScreen - contains the identifier of the screen that is opened by clicking the link enabled in the link attribute.

    • linkScreenOpenType - sets the screen opening mode (THIS_TAB, NEW_TAB or DIALOG).

    • linkInvoke - invokes the controller method instead of opening the screen.

    • captionProperty - the name of an entity attribute which should be shown in the column instead of specified by id. For example, if you have a reference to the Priority entity with the name and orderNo attributes, you can define the following column:

      <column id="priority.orderNo" captionProperty="priority.name" caption="msg://priority" />

      In this case, the column will display the priority name, but the sorting of the column will be done by the priority order.

    • column element may contain a nested formatter element that allows you to represent the attribute value in a format different from the standard for this Datatype:

      <column id="date">
          <formatter class="com.haulmont.cuba.gui.components.formatters.DateFormatter"
                     format="yyyy-MM-dd HH:mm:ss"/>
      </column>
  • rowsCount − an optional element adding the RowsCount component for the table; this component enables loading the table data in pages. Page size can be defined by limiting the number of records in the data source using CollectionDatasource.setMaxResults() method. Typically, this is performed by a Filter component linked to the table’s data source. However, if there is no generic filter, this method can be called directly from the screen controller.

    RowsCount component can also show the total number of records returned by the current query from the datasource without extracting the records themselves. It invokes AbstractCollectionDatasource.getCount() when user clicks the ? icon, which results in performing a database query with the same conditions as the current query, but using a COUNT(*) aggregate function instead. The number retrieved is displayed instead of the ? icon.

  • actions − an optional element describing the actions, related to the table. In addition to custom arbitrary actions, the element supports the following standard actions, defined in ListActionType enum: create, edit, remove, refresh, add, exclude, excel.

  • buttonsPanel – an optional element, which adds a ButtonsPanel container to show action buttons above the table.

table attributes:

  • multiselect attribute enables setting multiple selection mode for table rows. If multiselect is true, users can select multiple rows in the table using keyboard or mouse holding Ctrl or Shift keys. By default, multiple selection mode is switched off.

  • sortable attribute enables sorting data in the table. By default, it is set to true. If sorting is allowed, clicking a column header will show a gui_sortable_down/gui_sortable_up icon to the right of the column name. You can disable sorting for a particular column by using its sortable attribute.

    Table sorting can be performed differently depending on whether all the records can be placed on one page or not. If they can, sorting is performed in memory without database queries. If there is more than one page, sorting is performed in the database by sending a new query with the corresponding ORDER BY condition.

    A table column may refer to a local attribute or a linked entity. For example:

    <table id="ordersTable">
        <columns>
            <column id="customer.name"/> <!-- the 'name' attribute of the 'Customer' entity -->
            <column id="contract"/>      <!-- the 'Contract' entity -->
        </columns>
        <rows datasource="ordersDs"/>
    </table>

    In the latter case, the database sorting will be performed by attributes defined in the @NamePattern annotation of the related entity. If the entity has no such annotation, the sorting will be performed in memory only within the current page.

    If the column refers to a non-persistent entity attribute, the database sorting will be performed by attributes defined in the related() parameter of the @MetaProperty annotation. If no related attributes are specified, the sorting will be performed in memory only within the current page.

    If the table is connected to a nested datasource that contains a collection of related entities, the collection attribute must be of ordered type (List or LinkedHashSet) for table to be sortable. If the attribute is of type Set, the sortable attribute has no effect and the table cannot be sorted by users.

  • presentations attribute controls the mechanism of presentations. By default, the value is false. If the attribute value is true, a corresponding icon is added to the top right corner of the table gui_presentation. The mechanism of presentations is implemented for the Web Client only.

  • If the columnControlVisible attribute is set to false, users cannot hide columns using the drop-down menu of the gui_table_columnControl button in the right part of the table header. Currently displayed columns are marked with checkmarks in the menu.

gui table columnControl all
  • If the reorderingAllowed attribute is set to false, users cannot change columns order by dragging them with a mouse.

  • If the columnHeaderVisible attribute is set to false, the table has no header.

  • If the showSelection attribute is set to false, a current row is not highlighted.

  • contextMenuEnabled attribute enables the context menu. By default this attribute is set to true. The context menu shows table actions (if any) and the System Information item containing information on the selected entity (if the user has cuba.gui.showInfo permission).

  • Setting multiLineCells to true enables multi-line display for cells containing several lines of text. In this mode, the web browser will load all the rows of the current table page at once, instead of lazy-loading the visible part of the table. It is required for proper scrolling in the Web Client. The default value is false.

  • aggregatable attribute enables aggregation for table rows. The following operations are supported:

    • SUM – calculate the sum

    • AVG – find the average value

    • COUNT – calculate the total number

    • MIN – find the minimum value

    • MAX – find the maximum value

    The aggregation element should be set for aggregated table columns with the type attribute, which sets the aggregation function. The aggregated table values are shown in an additional row at the top of the table. An example of an aggregated table description:

    <table id="itemsTable" aggregatable="true">
        <columns>
            <column id="product"/>
            <column id="quantity"/>
            <column id="amount">
                <aggregation type="SUM"/>
            </column>
        </columns>
        <rows datasource="itemsDs"/>
    </table>

    The aggregation element can also contain the strategyClass attribute specifying a class implementing the AggregationStrategy interface (see below the example of setting an aggregation strategy programmatically).

    A Formatter can be specified to display the aggregated value in the format other than the standard for this Datatype:

    <column id="amount">
        <aggregation type="SUM">
            <formatter class="com.company.sample.MyFormatter"/>
        </aggregation>
    </column>

    The aggregationStyle attribute allows you to specify the location of the aggregation row: TOP or BOTTOM. TOP is used by default.

    In addition to the operations listed above, you can define a custom aggregation strategy by implementing the AggregationStrategy interface and passing it to the setAggregation() method of the Table.Column class inside the AggregationInfo instance. For example:

    public class TimeEntryAggregation implements AggregationStrategy<List<TimeEntry>, String> {
        @Override
        public String aggregate(Collection<List<TimeEntry>> propertyValues) {
            HoursAndMinutes total = new HoursAndMinutes();
            for (List<TimeEntry> list : propertyValues) {
                for (TimeEntry timeEntry : list) {
                    total.add(HoursAndMinutes.fromTimeEntry(timeEntry));
                }
            }
            return StringFormatHelper.getTotalDayAggregationString(total);
        }
        @Override
        public Class<String> getResultClass() {
            return String.class;
        }
    }
    AggregationInfo info = new AggregationInfo();
    info.setPropertyPath(metaPropertyPath);
    info.setStrategy(new TimeEntryAggregation());
    
    Table.Column column = weeklyReportsTable.getColumn(columnId);
    column.setAggregation(info);
  • editable attribute enables switching the table to in-place editing mode. In this mode, the columns with editable = true attribute show components to edit the attributes of the corresponding entity.

    The component type for each editable column is selected automatically based on the type of the corresponding entity attribute. For example, for string and numeric attributes, the application will use TextField, for DateDateField, for lists – LookupField, for links to other entities – PickerField.

    For a Date type editable column, you can additionally define dateFormat or resolution attributes similar to the ones described for the DateField.

    optionsDatasource and captionProperty attributes can be additionally defined for an editable column showing a linked entity. If optionsDatasource is set, the application will use LookupField instead of PickerField.

    Custom configuration (including editing) of a cell can be performed using Table.addGeneratedColumn() method – see below.

Methods of the Table interface:

  • getSelected(), getSingleSelected() return instances of the entities corresponding to the selected rows of the table. A collection can be obtained by invoking getSelected(). If nothing is selected, the application returns an empty set. If multiselect is disabled, it is more convenient to use getSingleSelected() method returning one selected entity or null, if nothing is selected.

  • addGeneratedColumn() method allows you to define custom representation of data in a column. It takes two parameters: identifier of the column and an implementation of the Table.ColumnGenerator interface. Identifier can match one of the identifiers set for table columns in XML-descriptor – in this case the new column is inserted instead of the one defined in XML. If the identifier does not match any of the columns, a new column is added to the right.

    generateCell() method of the Table.ColumnGenerator interface is invoked for each row of the table. The method receives an instance of the entity displayed in the corresponding row. generateCell() method should return a visual component which will be displayed in the cell.

    Example of using the component:

    @Inject
    protected Table carsTable;
    
    @Inject
    protected ComponentsFactory componentsFactory;
    
    @Override
    public void init(Map<String, Object> params) {
        carsTable.addGeneratedColumn("colour", new Table.ColumnGenerator() {
            @Override
            public Component generateCell(Entity entity) {
                LookupPickerField field = componentsFactory.createComponent(LookupPickerField.NAME);
                field.setDatasource(carsTable.getItemDatasource(entity), "colour");
                field.setOptionsDatasource(coloursDs);
                field.addLookupAction();
                field.addOpenAction();
                return field;
            }
        });
    }

    In the example above, all cells within the colour column in the table show the LookupPickerField component. The component should save its value into the colour attribute of the entity which instance is displayed in the corresponding row. For this purpose getItemDatasource() method is used to get the datasource for the current entity instance from the table and pass it to the LookupPickerField component.

    If you want to display just dynamic text, use special class Table.PlainTextCell instead of the Label component. It will simplify rendering and make the table faster.

    If addGeneratedColumn() method receives the identifier of a column which is not declared in XML-descriptor, the header for the new column to be set as follows:

    carsTable.getColumn("colour").setCaption("Colour");
  • setClickListener() method can save you from adding generated columns with components when you need to draw something in cells and receive notifications when a user clicks inside these cells. The CellClickListener implementation passed to this method receives the selected entity and the column identifier. The cells content will be wrapped in span element with cuba-table-clickable-cell style which can be used to specify the cell representation.

  • setStyleProvider() method enables setting table cell display style. The method accepts an implementation of Table.StyleProvider interface as a parameter. getStyleName() method of this interface is invoked by the table for each row and each cell separately. If the method is invoked for a row, the first parameter contains the entity instance displayed by the row, the second parameter is null. If the method is called for a cell, the second parameter contains the name of the attribute displayed by the cell.

    Example of setting a style:

    @Inject
    protected Table customersTable;
    
    @Override
    public void init(Map<String, Object> params) {
        customersTable.setStyleProvider(new Table.StyleProvider() {
            @Nullable
            @Override
            public String getStyleName(Entity entity, @Nullable String property) {
                Customer customer = (Customer) entity;
                if (property == null) {
                    // style for row
                    if (hasComplaints(customer)) {
                        return"unsatisfied-customer";
                    }
                } else if (property.equals("grade")) {
                    // style for column "grade"
                    switch (customer.getGrade()) {
                        case PREMIUM: return "premium-grade";
                        case HIGH: return "high-grade";
                        case MEDIUM: return "medium-grade";
                        default: return null;
                    }
                }
                return null;
            }
        });
    }

    Then the cell and row styles set in the application theme should be defined. Detailed information on creating a theme is available in Themes. For web client, new styles are defined in the styles.scss file. Style names defined in the controller, together with prefixes identifying table row and column form CSS selectors. For example:

    .v-table-row.unsatisfied-customer {
      font-weight: bold;
    }
    .v-table-cell-content.premium-grade {
      background-color: red;
    }
    .v-table-cell-content.high-grade {
      background-color: green;
    }
    .v-table-cell-content.medium-grade {
      background-color: blue;
    }
  • addPrintable() method enables setting a custom presentation of the data within a column when exporting to an XLS file via the excel standard action or directly using the ExcelExporter class. The method accepts the column identifier and an implementation of the Table.Printable interface for the column. For example:

    ordersTable.addPrintable("customer", new Table.Printable<Customer, String>() {
        @Override
        public String getValue(Customer customer) {
            return "Name: " + customer.getName;
        }
    });

    getValue() method of the Table.Printable interface should return data to be displayed in the table cell. This is not necessarily a string – the method may return values of other types, for example, numeric data or dates, which will be represented in the XLS file accordingly.

    If formatted output to XLS is required for a generated column, an implementation of the Table.PrintableColumnGenerator interface passed to the addGeneratedColumn() method should be used. The value for a cell in an XLS document is defined in the getValue() method of this interface:

    ordersTable.addGeneratedColumn("product", new Table.PrintableColumnGenerator<Order, String>() {
        @Override
        public Component generateCell(Order entity) {
            Label label = componentsFactory.createComponent(Label.NAME);
            Product product = order.getProduct();
            label.setValue(product.getName() + ", " + product.getCost());
            return label;
        }
    
        @Override
        public String getValue(Order entity) {
            Product product = order.getProduct();
            return product.getName() + ", " + product.getCost();
        }
    });

    If Printable presentation is not defined for a generated column in one way or another, then the column will either show the value of corresponding entity attribute or nothing if there is no associated entity attribute.

  • The setItemClickAction() method allows you to define an action that will be performed when a table row is double-clicked. If such action is not defined, the table will attempt to find an appropriate one in the list of its actions in the following order:

    • The action assigned to the Enter key by the shortcut property

    • The edit action

    • The view action

      If such action is found, and has enabled = true property, the action is executed.

  • The setEnterPressAction() allows you to define an action executed when Enter is pressed. If such action is not defined, the table will attempt to find an appropriate one in the list of its actions in the following order:

    • The action defined by the setItemClickAction() method

    • The action assigned to the Enter key by the shortcut property

    • The edit action

    • The view action

    If such action is found, and has enabled = true property, the action is executed.



4.5.2.1.26. TextArea

TextArea is a multi-line text editor field.

XML-name of the component: textArea

gui TextArea dia

TextArea component is implemented for both Web Client and Desktop Client.

TextArea mostly replicates the functionality of the TextField component except that a datatype can not be assigned to it. I.e. TextArea is intended to be used only for text and entity attributes of type String.

TextArea component has the following attributes:

  • cols and rows set the number of columns and rows of text:

    <textArea id="textArea" cols="20" rows="5" caption="msg://name"/>

    The values of width and height have priority over the values of cols and rows.

  • resizable – if this attribute is set to true and the number of rows is more than one, a user can change the size of the component:

    <textArea id="textArea" resizable="true" caption="msg://name" rows="5"/>
    gui textField resizable
  • wordwrap - set this attribute to false to turn off word wrapping.



4.5.2.1.27. TextField

TextField is a component for text editing. It can be used both for working with entity attributes and entering/displaying arbitrary textual information.

XML-name of the component: textField

gui TextField dia

TextField is implemented for both Web Client and Desktop Client.

  • An example of a text field with a caption retrieved from the localized messages pack:

    <textField id="nameField" caption="msg://name"/>

    The figure below shows an example of a simple text field.

    gui textField data
  • To create a text field connected to data, use datasource and property attributes.

    <dsContext>
        <datasource id="customerDs" class="com.sample.sales.entity.Customer" view="_local"/>
    </dsContext>
    <layout>
        <textField datasource="customerDs" property="name" caption="msg://name"/>

    As you can see in the example, the screen describes the customerDs datasource for Customer entity, which has name attribute. The text field component has a link to the data source specified in the datasource attribute; property attribute contains the name of the entity attribute that is displayed in the text field.

  • If the field is not connected to an entity attribute (i.e. the data source and attribute name are not set), you can set the data type using the datatype attribute. It is used to format field values. The attribute value accepts any data type registered in the application metadata – see Datatype. Typically, TextField uses the following data types:

    • decimal

    • double

    • int

    • long

      As an example, let’s look at a text field with an Integer data type.

      <textField id="integerField" datatype="int" caption="msg://integerFieldName"/>

      If a user enters a value that cannot be interpreted as an integer number, then when the field looses focus, the application will show an error message and revert field value to the previous one.

  • Text field can be assigned a validator – a class implementing Field.Validator interface. The validator limits user input in addition to what is already done by the datatype. For example, to create an input field for positive integer numbers, you need to create a validator class:

    public class PositiveIntegerValidator implements Field.Validator {
        @Override
        public void validate(Object value) throws ValidationException {
            Integer i = (Integer) value;
            if (i <= 0)
                throw new ValidationException("Value must be positive");
        }
    }

    and assign it as a validator to the text field with int datatype:

    <textField id="integerField" datatype="int">
        <validator class="com.sample.sales.gui.PositiveIntegerValidator"/>
    </textField>

    Unlike input check against the data type, validation is performed not when the field looses focus, but after invocation of the field’s validate() method. It means that the field (and the linked entity attribute) may temporarily contain a value that does not satisfy validation conditions (a non-positive number in the example above). This should not be an issue, because validated fields are typically used in editor screens, which automatically invoke validation for all their fields before commit. If the field is located not in an editing screen, the field’s validate() method should be invoked explicitly in the controller.

  • If a text field is linked to an entity attribute (via datasource and property), and if the entity attribute has a length parameter defined in the @Column JPA-annotation, then the TextField will limit the maximum length of entered text accordingly.

    If a text field is not linked to an attribute, or if the attribute does not have length value defined, or this value needs to be overridden, then the maximum length of the entered text can be limited using maxLength attribute. The value of "-1" means there are no limitations. For example:

    <textField id="shortTextField" maxLength="10"/>
  • By default, text field trims spaces at the beginning and at the end of the entered string. I.e. if user enters " aaa bbb ", the value of the field returned by the getValue() method and saved to the linked entity attribute will be "aaa bbb". You can disable trimming of spaces by setting the trim attribute to false.

    It should be noted that trimming only works when users enter a new value. If the value of the linked attribute already has spaces in it, the spaces will be displayed until user edits the value.

  • Text field always returns null instead of an entered empty string. Therefore, with the trim attribute enabled, any string containing spaces only will be converted to null.

  • The setCursorPosition() method can be used to focus the field and set the cursor position to the specified 0-based index.



4.5.2.1.28. TimeField

TimeField is a field for displaying and entering time.

gui timeField

XML-name of the component: timeField.

gui timeField dia

TimeField component is implemented for both Web Client and Desktop Client.

  • To create a time field associated with data, datasource and property attributes should be used:

    <dsContext>
        <datasource id="orderDs" class="com.sample.sales.entity.Order" view="_local"/>
    </dsContext>
    <layout>
        <timeField datasource="orderDs" property="deliveryTime"/>

    As you can see in the example above, the screen defines the orderDs data source for Order entity, which has deliveryTime attribute. The datasource attribute of the time input component contains a link to the datasource, and the property attribute – the name of the entity attribute displayed in the field.

    Related entity attribute should have java.util.Date or java.sql.Time type.

  • The time format is defined by the time datatype and is specified in the main localized messages pack in the timeFormat key.

  • The time format can also be specified in the timeFormat attribute. It can be either a format string, or a key in a message pack (with the msg:// prefix).

  • Regardless of the mentioned above format display of seconds can be controlled using showSeconds attribute. By default, seconds are displayed if the format contains ss.

    <timeField datasource="orderDs" property="createTs" showSeconds="true"/>
    gui timeFieldSec

Attributes of timeField

align - caption - editable - enable - datasource - description - height - icon - id - property - required - requiredMessage - showSeconds - stylename - timeFormat - visible - width

Elements of timeField

validator


4.5.2.1.29. TokenList

TokenList component offers a simplified way of working with lists: instance names are listed vertically or horizontally, adding is done using drop-down list, removal – using the buttons located near each instance.

gui tokenList

XML-name of the component: tokenList

gui TokenList dia

The component is implemented for both Web Client and Desktop Client.

Below is an example description of TokenList in an XML-descriptor of a screen:

<dsContext>
    <datasource id="orderDs"
                class="com.sample.sales.entity.Order"
                view="order-edit">
        <collectionDatasource id="productsDs" property="products"/>
    </datasource>
    <collectionDatasource id="allProductsDs"
                          class="com.sample.sales.entity.Product"
                          view="_minimal">
        <query>select p from sales$Product p order by p.name</query>
    </collectionDatasource>
</dsContext>
<layout>
    <tokenList id="productsList" datasource="productsDs" inline="true" width="500px">
        <lookup optionsDatasource="allProductsDs"/>
    </tokenList>

In the example above, the nested productsDs datasource which includes a collection of products within an order is defined, as well as allProductsDs datasource containing a collection of all products available in the database. The TokenList component with productsList identifier displays the content of the productsDs datasource and enables changing the collection by adding instances from allProductsDs.

tokenList attributes:

  • position – sets the position for the drop-down list. The attribute can take two values: TOP, BOTTOM. Default is TOP.

gui tokenListBottom
  • inline attribute defines how the list with selected items will be displayed: vertically or horizontally. true corresponds to horizontal alignment, false – to vertical. An example of a component with horizontal alignment:

gui tokenListInline
  • simple – when set to true, the items selection component will be hidden with only the Add button left. Clicking the Add button opens the screen with the list of entity instances which type is defined by the datasource. Selection screen identifier is selected according to the rules for the PickerField.LookupAction standard action.

gui tokenListSimple

tokenList elements:

  • lookup − values selection component descriptor.

    Attributes of the lookup element:

    • lookup attribute makes it possible to select items using an entity lookup screen:

      gui tokenListLookup
    • lookupScreen attribute sets the identifier of the screen used for items selection in lookup="true" mode. If this attribute is not set, screen identifier is selected according to the rules for the PickerField.LookupAction standard action.

    • openType attribute defines how the lookup screen will be opened, similar to what is described for the PickerField.LookupAction standard action. Default value – THIS_TAB.

    • multiselect - if this attribute’s value is set to true, then true will be passed to parameters map of the lookup screen for the MULTI_SELECT key. This flag can be used to set the screen into multiple selection mode. This flag is defined in the WindowParams enum so it is convenient to work with it in the following way:

      @Override
      public void init(Map<String, Object> params) {
          if (WindowParams.MULTI_SELECT.getBool(getContext())) {
              usersTable.setMultiSelect(true);
          }
      }

      The multiselect attribute works only if the simple attribute is set to true.

  • button – descriptor of the button for adding items. Can contain caption and icon attributes.



4.5.2.1.30. Tree

The Tree component is intended to display hierarchical structures represented by entities referencing themselves.

gui Tree

XML-name of the component: tree

gui tree dia

The component is implemented for both Web Client and Desktop Client.

For the Tree component, the datasource attribute of the treechildren element should contain a reference to a hierarchicalDatasource. Declaration of a hierarchicalDatasource should contain a hierarchyProperty attribute – the name of the entity attribute which is a reference to same entity.

Below is an example of the Tree component description in a screen XML-descriptor:

<dsContext>
    <hierarchicalDatasource id="departmentsDs" class="com.sample.sales.entity.Department" view="browse"
                            hierarchyProperty="parentDept">
        <query>
            select d from sales$Department d order by d.createTs
        </query>
    </hierarchicalDatasource>
</dsContext>
<layout>
    <tree id="departmentsTree" width="100%" height="100%">
        <treechildren datasource="departmentsDs" captionProperty="name"/>
    </tree>

The name of the entity attribute to be displayed in the tree can be set using the captionProperty attribute of the treechildren element. If this attribute is not defined, the screen will show the entity instance name.

multiselect attribute enables setting multiple selection mode for tree items. If multiselect is true, users can select multiple items using keyboard or mouse holding Ctrl or Shift keys. By default, multiple selection mode is switched off.

The setItemClickAction() method may be used to define an action that will be performed when a tree node is double-clicked.

Each tree item can have an icon on the left. Create an implementation of the ListComponent.IconProvider interface in the screen controller and set it for the tree:

@Inject
private Tree<Region> tree;

@Override
public void init(Map<String, Object> params) {
    tree.setIconProvider(new ListComponent.IconProvider<Region>() {
        @Nullable
        @Override
        public String getItemIcon(Region entity) {
            if (entity.getParent() == null) {
                return "icons/root.png";
            }
            return "icons/leaf.png";
        }
    });
}

Attributes of tree

enable - height - id - multiselect - stylename - visible - width

Elements of tree

actions - treechildren

Attributes of treechildren

captionProperty - datasource


4.5.2.1.31. TreeTable

TreeTable component is a hierarchical table displaying a tree-like structure in the leftmost column. The component is used for entities that have references to themselves. For example, it can be a file system or a company organization chart.

gui treeTable

XML-name of the component: treeTable

gui TreeTable dia

The component is implemented for both Web Client and Desktop Client.

For TreeTable, the hierarchicalDatasource should be set in the datasource attribute of the rows element. Declaration of a hierarchicalDatasource should contain hierarchyProperty attribute – the name of the entity attribute which references the same entity.

Below is an example of component description in a screen XML descriptor:

<dsContext>
    <hierarchicalDatasource id="tasksDs" class="com.sample.sales.entity.Task" view="browse"
                            hierarchyProperty="parentTask">
        <query>
            select t from sales$Task t
        </query>
    </hierarchicalDatasource>
</dsContext>
<layout>
    <treeTable id="tasksTable" width="100%">
        <columns>
            <column id="name"/>
            <column id="dueDate"/>
            <column id="assignee"/>
        </columns>
        <rows datasource="tasksDs"/>
    </treeTable>

The functionality of TreeTable is similar to a simple Table.



4.5.2.1.32. TwinColumn

TwinColumn is a twin list component for multiple items selection. The left part of the list contains available unselected values, the right part – selected values. Users select the values by transferring them from the left to the right and backward using double click or dedicated buttons. A unique representation style and an icon can be defined for each value.

TwinColumn

XML name of the component: twinColumn

gui TwinColumn dia

The component is implemented for Web Client only.

Below is an example of a twinColumn component usage to select entity instances:

<dsContext>
    <datasource id="carDs" class="com.company.sample.entity.Car" view="_local"/>
    <collectionDatasource id="coloursDs" class="com.company.sample.entity.Colour" view="_local">
        <query>select c from sample$Colour c</query>
    </collectionDatasource>
</dsContext>
<layout>
    <twinColumn id="coloursField" optionsDatasource="coloursDs" addAllBtnEnabled="true"/>

In this example, the coloursField component will display names of Colour entity instances located in the coloursDs data source and its getValue() method will return a collection of selected instances.

addAllBtnEnabled attribute shows the buttons moving all items between the lists.

columns attribute is used to set the number of characters in a row, and the rows attribute – to set the number of rows in each list.

The presentation of the items can be defined by implementing the TwinColumn.StyleProvider interface and returning a style name and icon path for each entity instance displayed in the component.

The list of component options can be specified arbitrarily using the setOptionsList(), setOptionsMap() and setOptionsEnum() methods as described for the OptionsGroup component.



4.5.2.2. Containers
4.5.2.2.1. BoxLayout

BoxLayout is a container with sequential placement of components.

There are three types of BoxLayout, identified by the XML-elements:

  • hbox − components are placed horizontally.

    gui hbox
    <hbox spacing="true" margin="true">
        <dateField datasource="orderDs" property="date"/>
        <lookupField datasource="orderDs" property="customer" optionsDatasource="customersDs"/>
        <textField datasource="orderDs" property="amount"/>
    </hbox>
  • vbox − components are placed vertically. vbox has 100% width by default.

    gui vbox
    <vbox spacing="true" margin="true">
        <dateField datasource="orderDs" property="date"/>
        <lookupField datasource="orderDs" property="customer" optionsDatasource="customersDs"/>
        <textField datasource="orderDs" property="amount"/>
    </vbox>
  • flowBox − components are placed horizontally with line wrapping. If there is not enough space in a line, the components that do not fit will be displayed in the next line (the behavior is similar to Swing FlowLayout).

    gui flowbox
    <flowBox spacing="true" margin="true">
        <dateField datasource="orderDs" property="date"/>
        <lookupField datasource="orderDs" property="customer" optionsDatasource="customersDs"/>
        <textField datasource="orderDs" property="amount"/>
    </flowBox>

Attributes of hbox, vbox, flowBox

align - enable - expand - height - id - margin - spacing - stylename - visible - width


4.5.2.2.2. ButtonsPanel

ButtonsPanel is a container that streamlines the use and placement of the components (usually, buttons) for data management in a table.

gui buttonsPanel

XML-name of the component: buttonsPanel.

A sample definition of ButtonsPanel in screen XML-descriptor:

<table id="customersTable"
       editable="false" width="100%">
    <actions>
        <action id="create"/>
        <action id="edit"/>
        <action id="remove"/>
        <action id="excel"/>
    </actions>
    <buttonsPanel>
        <button action="customersTable.create"/>
        <button action="customersTable.edit"/>
        <button action="customersTable.remove"/>
        <button action="customersTable.excel"/>
    </buttonsPanel>
    <columns>
        <column id="name"/>
        <column id="email"/>
    </columns>
    <rows datasource="customersDs"/>
</table>

buttonsPanel element can be located either inside a table, or in any other place of a screen.

If the buttonsPanel is located in a table, it is combined with the table’s rowsCount component thus using vertical space more effectively. Additionally, if a lookup screen is opened using Frame.openLookup() (for example, from the PickerField component) the buttons panel becomes hidden.

alwaysVisible attribute disables panel hiding in a lookup screen when it is opened by Frame.openLookup(). If the attribute value is true, the buttons panel is not hidden. By default, the attribute value is false.


Attributes of buttonsPanel

align - alwaysVisible - enable - expand - height - id - stylename - visible - width


4.5.2.2.3. CssLayout

CssLayout is a container that enables full control over placement and styling of enclosed components using CSS.

XML-name of the component: cssLayout.

Below is an example of using cssLayout for simple responsive screen.

Displaying components on a wide screen:

gui cssLayout 1

Displaying components on a narrow screen:

gui cssLayout 2

Screen’s XML-descriptor:

<cssLayout responsive="true"
           stylename="responsive-container"
           width="100%">
    <vbox margin="true"
          spacing="true"
          stylename="group-panel">
        <textField caption="Field One" width="100%"/>
        <textField caption="Field Two" width="100%"/>
        <button caption="Button"/>
    </vbox>
    <vbox margin="true"
          spacing="true"
          stylename="group-panel">
        <textField caption="Field Three" width="100%"/>
        <textField caption="Field Four" width="100%"/>
        <button caption="Button"/>
    </vbox>
</cssLayout>

Content of modules/web/themes/halo/halo-ext.scss file (see Extending an Existing Theme for how to create this file):

@import "../halo/halo";

@mixin halo-ext {
  @include halo;

  .responsive-container {
    &[width-range~="0-900px"] {
      .group-panel {
        width: 100% !important;
      }
    }

    &[width-range~="901px-"] {
      .group-panel {
        width: 50% !important;
      }
    }
  }
}

responsive attribute indicates that the component should react on change in the available space. It is false by default.


Attributes of cssLayout

enable - height - id - responsive - stylename - visible - width


4.5.2.2.4. Frame

frame element is designed to include frames into a screen.

Attributes:

  • src − path to the frame XML-descriptor.

  • screen – frame identifier in screens.xml (if the frame is registered).

One of these attributes should be defined. If both attributes are defined, frame will be loaded from the file explicitly set in src.


Attributes of frame

align - height - id - screen - src - stylename - visible - width


4.5.2.2.5. GridLayout

GridLayout container places components on a grid.

gui gridlayout

XML-name of the component: grid.

Example container usage:

<grid spacing="true">
    <columns count="4"/>
    <rows>
        <row>
            <label value="Date" align="MIDDLE_LEFT"/>
            <dateField datasource="orderDs" property="date"/>
            <label value="Customer" align="MIDDLE_LEFT"/>
            <lookupField datasource="orderDs" property="customer"
                         optionsDatasource="customersDs"/>
        </row>
        <row>
            <label value="Amount" align="MIDDLE_LEFT"/>
            <textField datasource="orderDs" property="amount"/>
        </row>
    </rows>
</grid>

grid elements:

  • columns – a required element, describes grid columns. It should have either a count attribute, or a nested column element for each column.

    In the simplest case, it is enough to set the number of columns in the count attribute. Then, if the container width is explicitly defined in pixels or percents, free space will be divided between the columns equally.

    In order to divide screen space non-equally, a column element with a flex attribute should be defined for each column.

    An example of a grid where the second and the fourth columns take all extra horizontal space and the fourth column takes three times more space:

    <grid spacing="true" width="100%">
        <columns>
            <column/>
            <column flex="1"/>
            <column/>
            <column flex="3"/>
        </columns>
        <rows>
            <row>
                <label value="Date"/>
                <dateField datasource="orderDs" property="date" width="100%"/>
                <label value="Customer"/>
                <lookupField datasource="orderDs" property="customer"
                             optionsDatasource="customersDs" width="100%"/>
            </row>
            <row>
                <label value="Amount"/>
                <textField datasource="orderDs" property="amount" width="100%"/>
            </row>
        </rows>
    </grid>

    If flex is not defined, or is set to 0, the width of the column will be set according to its contents given that at least one other column has a non-zero flex. In the example above, the first and the third columns will get the width according to the maximum text length.

    Tip

    In order for the free space to appear, the entire container width should be set in either pixels or percents. Otherwise, column width will be calculated according to content length, and flex attribute will have no effect.

  • rows − a required element, contains a set of rows. Each line is defined in its own row element.

    row element can have a flex attribute similar to the one defined for column, but affecting the distribution of free vertical space with a given total grid height.

    row element should contain elements of the components displayed in the grid’s current row cells. The number of components in a row should not exceed the defined number of columns, but it can be less.

Any component located in a grid container can have colspan and rowspan attributes. These attributes set the number of columns and rows occupied by the corresponding component. For example, this is how Field3 field can be extended to cover three columns:

<grid spacing="true">
    <columns count="4"/>
    <rows>
        <row>
            <label value="Field1"/>
            <textField/>
            <label value="Field2"/>
            <textField/>
        </row>
        <row>
            <label value="Field3"/>
            <textField colspan="3" width="100%"/>
        </row>
    </rows>
</grid>

As a result, the components will be placed in the following way:

gui gridlayout colspan

Attributes of grid

align - enable - height - id - margin - spacing - stylename - visible - width

Elements of grid

columns - rows

Attributes of columns

count

Attributes of column

flex

Attributes of row

flex - visible


4.5.2.2.6. GroupBoxLayout

GroupBoxLayout is a container that enables framing the embedded components and setting a universal header for them. Additionally, it can collapse content.

gui groupBox

Component XML-name: groupBox.

An example container description in a screen XML-descriptor:

<groupBox caption="Order">
    <dateField datasource="orderDs" property="date" caption="Date"/>
    <lookupField datasource="orderDs" property="customer"
                 optionsDatasource="customersDs" caption="Customer"/>
    <textField datasource="orderDs" property="amount" caption="Amount"/>
</groupBox>

groupBox attributes:

  • caption – group header.

  • orientation – defines components placement direction − horizontal or vertical. The default value is vertical.

  • collapsable – if the value is set to true, the component’s content can be hidden using gui_groupBox_minus/gui_groupBox_plus buttons.

  • collapsed – if set to true, component’s content will be collapsed initially. It is used with collapsable="true".

    An example of a collapsed GroupBox:

gui groupBox collapsed

By default, the groupBox container is 100% wide, similar to vbox.


Attributes of groupBox

align - caption - collapsable - collapsed - expand - height - id - orientation - spacing - stylename - width


4.5.2.2.7. HtmlBoxLayout

HtmlBoxLayout is a container that enables you to define locations of components in an HTML template. The layout template is included in a theme.

XML-name of the component: htmlBox.

Below is an example of using htmlBox for a simple screen.

gui htmlBox 1

Screen’s XML-descriptor:

<htmlBox align="TOP_CENTER"
         template="sample"
         width="500px">
    <label id="logo"
           value="Subscribe"
           stylename="logo"/>
    <textField id="email"
               width="100%"
               inputPrompt="email@test.test"/>
    <button id="submit"
            width="100%"
            invoke="showMessage"
            caption="Subscribe"/>
</htmlBox>

template attribute defines the name of a HTML file located in the layouts subdirectory of your theme. You should create a theme extension or a custom theme before creating a template. For example, if your theme is Halo and the attribute contains my_template, the template file should be modules/web/themes/halo/layouts/my_template.html.

Content of the HTML templated located in the modules/web/themes/halo/layouts/sample.html file:

<div location="logo" class="logo"></div>
<table class="component-container">
    <tr>
        <td>
            <div location="email" class="email"></div>
        </td>
        <td>
            <div location="submit" class="submit"></div>
        </td>
    </tr>
</table>

A template should contain <div> elements with location attributes. This elements will display CUBA components defined in the XML descriptor with corresponding identifiers.

Content of modules/web/themes/halo/halo-ext.scss file (see Extending an Existing Theme for how to create this file):

@import "../halo/halo";

@mixin halo-ext {
  @include halo;

  .email {
    width: 390px;
  }

  .submit {
    width: 100px;
  }

  .logo {
    font-size: 96px;
    text-transform: uppercase;
    margin-top: 50px;
  }

  .component-container {
    display: inline-block;
    vertical-align: top;
    width: 100%;
  }
}

Attributes of htmlBox

align - enable - height - id - stylename - template - visible - width


4.5.2.2.8. ScrollBoxLayout

ScrollBoxLayout − a container that supports content scrolling.

gui scrollBox

Component XML-name: scrollBox

An example container description in a screen XML-descriptor:

<groupBox caption="Order" width="300" height="170">
    <scrollBox width="100%" height="100%" spacing="true" margin="true">
        <dateField datasource="orderDs" property="date" caption="Date"/>
        <lookupField datasource="orderDs" property="customer" optionsDatasource="customersDs" caption="Customer"/>
        <textField datasource="orderDs" property="amount" caption="Amount"/>
    </scrollBox>
</groupBox>
  • The components placement direction can be defined by orientation attribute − horizontal`or `vertical. Default is vertical.

  • scrollBars attribute enables configuring scroll bars. It can be horizontal, vertical – for horizontal and vertical scrolling respectively, both – for scrolling in both directions. Setting the value to none forbids scrolling in any direction.

Warning

The components placed in the scrollBox should have fixed size or default size. Do not set the size of nested components to height="100%" or width="100%".

At the same time, scrollBox itself cannot calculate its own size based on its content. So you should either specify an absolute size in pixels, or stretched scrollBar in the parent container by setting height="100%" and width="100%".


Attributes of scrollBox

align - height - id - margin - orientation - scrollBars - spacing - stylename - width


4.5.2.2.9. SplitPanel

SplitPanel − a container divided into two areas by a movable separator.

gui splitPanel

Component XML-name: split.

An example description of a split panel in a screen XML-descriptor:

<split orientation="horizontal" pos="30" width="100%" height="100%">
    <vbox margin="true" spacing="true">
        <dateField datasource="orderDs" property="date" caption="Date"/>
        <lookupField datasource="orderDs