CUBA Platform. Developer's Manual v6.3
CUBA Platform. Developer's Manual v6.3
html
Version 6.3
Copyright (c) 2016 Haulmont (www.haulmont.com)
Table of contents
Preface
Introduction
Installation and Setup
Quick Start
The Framework
Application Development
Application Deployment
Working with Databases
Security Subsystem
Appendix A: Configuration Files
Appendix B: Application Properties
Appendix C: System Properties
Glossary
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:
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.
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.
Memory – 4 GB
Hard drive space – 5 GB
Operating system: Microsoft Windows, Linux or Mac OS X
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 Computer → Properties → Advanced System Settings → Advanced → Environment
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.
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.
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.
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.
Name
Email
Order attributes:
Reference to a customer
Date
Amount
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.
Click OK. The entity designer page will be displayed in the workspace.
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 button next to the Name to open the Localized message window. Specify localization for the
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.
Now click 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.
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 name − Order.
The entity should have the following attributes:
Specify localized caption for each of the attributes by clicking the button next to the attribute name.
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.
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 [] 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.
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.
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.
order-edit.xml and order-browse.xml items will appear in the Web 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.
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.
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.
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.
Log in selecting English language in the login window. Open the Sales > Customers menu item:
Click Create:
Click Create:
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 perspectives: by tiers, blocks, modules
and components.
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.
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.
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.
cuba – the main component 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.
bpm – the mechanism of business processes execution according to the BPMN 2.0 standard.
An application always project depends on one ore more application components. It means that the
application uses the component as a library and includes its functionality.
Any CUBA application depends on the cuba component. Other platform components are optional and can
be included to the application only if needed. All optional components depend on cuba and can also contain
dependencies between each other.
Below is a diagramm showing dependencies between the platform application components. Solid lines
Any CUBA application can, in turn, be used as a component of another application. It enables
decomposition of large projects to a set of functional modules, which can be developed independently. You
can also create a set of custom application components in your organization and use them in multiple
projects, effectively creating your own higher-level platform on top of CUBA. Below is an example of a
possible structure of dependencies between application components.
See step-by-step guide to working with a custom application component in the Using Application
Components section.
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 a component
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 components.
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.
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.
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.
Instance – declares the basic methods for working with objects of application domain:
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; 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.
BaseGenericIdEntity – base class of persistent entities. It implements Entity but does not specify
the type of the identifier (i.e. the primary key) of the entity.
EmbeddableEntity - base class of embeddable persistent entities.
Below we consider base classes recommended for inheriting your entities from.
StandardEntity
Inherit from StandardEntity if you want a standard set of features: the primary key of UUID type, the
instances have information on who and when created and modified them, require optimistic locking and
soft deletion.
BaseUuidEntity
Inherit from BaseUuidEntity if you want an entity with the primary key of UUID type but you don’t need
all features of StandardEntity. You can implement some of the interfaces Creatable, Versioned,
etc. in your concrete entity class.
BaseLongIdEntity
Inherit from BaseLongIdEntity or BaseIntegerIdEntity if you want an entity with the primary key
of the Long or Integer type. You can implement some of the interfaces Creatable, Versioned, etc.
in your concrete entity class. Implementing HasUuid is highly recommended, as it enables some
optimizations and allows you to identify your instances uniquely in a distributed environment.
BaseStringIdEntity
Inherit from BaseStringIdEntity if you want an entity with the primary key of the String type. You
can implement some of the interfaces Creatable, Versioned, etc. in your concrete entity class.
Implementing HasUuid is highly recommended, as it enables some optimizations and allows you to
identify your instances uniquely in a distributed environment. The concrete entity class must have a string
field annotated with the @Id JPA annotation.
BaseIdentityIdEntity
Inherit from BaseIdentityIdEntity if you need to map the entity to a table with IDENTITY primary
key. You can implement some of the interfaces Creatable, Versioned, etc. in your concrete entity
class. Implementing HasUuid is highly recommended, as it enables some optimizations and allows you
to identify your instances uniquely in a distributed environment. The id attribute of the entity (i.e.
getId/setId methods) will be of type IdProxy which is designed to substitute the real identifier until it is
generated by the database on insert.
BaseGenericIdEntity
Inherit from BaseGenericIdEntity directly if you need to map the entity to a table with a composite
key. In this case, the concrete entity class must have a field of the embeddable type representing the key,
annotated with the @EmbeddedId JPA annotation.
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.
Defines an embedded entity stored in the same table as the owning entity.
@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.
@Entity
See javax.persistence.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")
@Extends
Indicates that the entity is an extension and it should be used everywhere instead of the base entity. See
Functionality Extension.
@DiscriminatorColumn
See 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:
Example:
@DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.INTEGER)
@DiscriminatorValue
See javax.persistence.DiscriminatorValue.
Example:
@DiscriminatorValue("0")
@Inheritance
See 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:
@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"
})
@MappedSuperclass
See 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.
@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")
@NamePattern
Determines the way of getting the name of the instance returned by the
Instance.getInstanceName() method.
{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")
@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.
@PrimaryKeyJoinColumn
See 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:
Example:
@PrimaryKeyJoinColumn(name = "CARD_ID", referencedColumnName = "ID")
@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.
@Table
See javax.persistence.Table.
Parameters:
Example:
@Table(name = "SALES_CUSTOMER")
@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.
@Column
See javax.persistence.Column.
Parameters:
@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.
@Embedded
See javax.persistence.Embedded.
Defines a reference attribute of embeddable type. The referenced entity should have @Embeddable
annotation.
Example:
@Embedded
protected Address address;
@EmbeddedParameters
By default, ORM does not create an instance of embedded entity if all its attributes are null in the
database. You can use the @EmbeddedParameters annotation to specify a different behavior when an
instance is always non-null, for example:
@Embedded
@EmbeddedParameters(nullAllowed = false)
protected Address address;
@Id
See 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).
@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).
@JoinColumn
See javax.persistence.JoinColumn.
Defines DB column that determines the relationship between entities. Presence of this annotation
indicates the owning side of the association.
Parameters:
Example:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CUSTOMER_ID")
protected Customer customer;
@JoinTable
See javax.persistence.JoinTable.
Parameters:
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;
@Lob
See 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;
@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;
@ManyToMany
See javax.persistence.ManyToMany.
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
@ManyToOne
See javax.persistence.ManyToOne.
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;
@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
@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;
@OneToMany
See javax.persistence.OneToMany.
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;
@OneToOne
See javax.persistence.OneToOne.
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.
@OrderBy
See 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:
Example:
@OneToMany(mappedBy = "user")
@OrderBy("createTs")
protected List<UserRole> userRoles;
@Temporal
See javax.persistence.Temporal.
Specifies the type of the stored value for java.util.Date attribute: date, time or date+time.
Parameters:
Example:
@Column(name = "START_DATE")
@Temporal(TemporalType.DATE)
@Transient
See javax.persistence.Transient.
@Version
See 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;
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;
PREMIUM(10),
HIGH(20),
MEDIUM(30);
CustomerGrade(Integer id) {
this.id = id;
}
@Override
public Integer getId() {
return id;
}
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.
Enumerations can be created in CUBA Studio on the Enumerations tab. To be used as an entity attribute,
choose ENUM in the Attribute type field of the attribute editor and select the Enumeration class in the Type
field. Enumeration values can be associated with localized names that will be displayed in the user interface
of the application.
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.
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.
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:
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.
@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.
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
Customer – Job 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.
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)
provides API for obtaining information about entities, their attributes and relations between the entities; it
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.
Example:
@Inject
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");
MetaProperty
Entity attribute metadata interface.
Basic methods:
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:
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.
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.
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;
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:
All the listed formats are specified in the main localized message pack of CUBA application component by
default, and can be overridden in the similar files of the application project.
order-browse.xml
<table id="ordersTable">
...
<columns>
<column id="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.
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());
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;
// 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";
@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);
@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);
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 application component. 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"/>
<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.
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
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.
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.
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.
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.
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.
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.
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.
Two views named _local and _minimal are available in the views repository for each entity by default:
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>
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.
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.
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.
import com.sample.sales.entity.Order;
import org.springframework.stereotype.Component;
@Component(OrderWorker.NAME)
public class OrderWorker {
public static final String NAME = "sales_OrderWorker";
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.
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.
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.
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.
import org.springframework.jmx.export.annotation.*;
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.1. CachingFacadeMBean
CachingFacadeMBean provides methods to clear various caches in the Middleware and Web Client
blocks.
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.
4.2.5.2.4. PersistenceManagerMBean
4.2.5.2.5. ScriptingManagerMBean
ScriptingManagerMBean is the JMX facade for the Scripting infrastructure interface.
JMX attributes:
RootPath – absolute path to the configuration directory of the Middleware block, in which this bean was
started.
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.*
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.
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
@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.
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);
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:
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
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:
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:
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:
a. 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;
b. 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);
}
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:
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);
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.
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();
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.
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;
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;
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.
See DataManager vs. EntityManager for information on differences between DataManager and
EntityManager.
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 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.
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:
When the application is started, AppContext is initialized using loader classes, specific for each application
block:
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");
}
});
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
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.appWindowMode 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.
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 these 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.
Additive Properties
Properties representing configuration parameters can be additive. Additive properties get their values
from all components used in the application. This allows platform mechanisms to configure your
application depending on the properties provided by application components. For example,
cuba.persistenceConfig is an additive property. In your project, it specifies a persistence.xml file
defining your project’s data model. But due to the fact that the real property value will include also
persistence.xml files of the application components, the whole data model of your application will
Additive properties are stored in files and usually have the plus sign in the beginning of values, indicating
that the property value will be assembled from application components at runtime. If you omit + for an
additive property, its value will be obtained only from the current project. It can be useful if you don’t want
to inherit some configuration from components, for example, when you define a menu structure.
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:com/company/sample/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
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.
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.
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.
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();
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().
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.
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.
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
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();
@Default("sec$Role-a294aef0-3ac9-11e2-9433-3860770d7eaf")
Role getAdminRole();
This section describes the localization mechanism and rules of localized messages creation. For information
about obtaining messages see Getting Localized Messages.
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 property files with the following specifics:
someMessage=Some Message
...
Messages are retrieved from the packs using Messages interface methods according to the following rules:
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.
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 application components packs can be overridden in the application project.
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:
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
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.
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:
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:
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:
The client block has to pass the so-called trusted password, specified in the cuba.trustedClientPassword
Middleware and client block application property.
IP address of the client block has to be in the list specified in the cuba.trustedClientPermittedIpList
application property.
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.
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
}
});
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.
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:
For example:
@SupportedByClient
@Logging(Logging.Type.BRIEF)
public class FinancialTransactionException extends Exception {
...
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.
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");
}
...
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");
}
...
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.
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
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.
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.
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.
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'
}
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.
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
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:
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.
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]+$.
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:
Groovy scripts are executed only by the server mechanism to launch database scripts.
def p = AppBeans.get(Persistence.class)
def tr = p.createTransaction()
try {
def em = p.getEntityManager()
em.persist(c)
tr.commit()
} finally {
tr.end()
}
})
To run scripts to create the database, the createDb task is used. In Studio, it corresponds to the Run →
Create database command in main menu. When this task is started, the following occurs:
1. Scripts of the application components 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 application components
are located in subdirectories with numeric prefixes. The prefixes are used to provide the alphabetical
order of the execution of scripts according to the dependencies between components.
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 Run →
Update database command in main menu. When this task is started, the following occurs:
1. The scripts are built in the same way as for the createDb command described above.
2. The execution mechanism checks, whether all creation scripts of application components have been run
(by checking the SYS_DB_CHANGELOG table). If not, the application component creation scripts are
executed and registered in the SYS_DB_CHANGELOG table.
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.
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 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 application component (see database scripts directory: 10-cuba, 20-bpm, …) 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 creation scripts of application
components have been run (by checking the SYS_DB_CHANGELOG table). If the database is not
initialized for use of some application component, 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.
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. In other
words, a service is an entry point to the Middleware business logic. In a service, you can manage
transactions, check user permissions, work with the database or delegate execution to other managed
beans of the middle tier.
The service interface is located in the global module and is available for both middle and client tiers. At
runtime, a proxy is created for the service interface on the client tier. The proxy provides invocation of
service bean methods using Spring HTTP Invoker mechanism.
The service implementation bean is located in the core module and is available on the middle tier only.
ServiceInterceptor is called automatically for any service method using Spring AOP. It checks the
availability of the user session for the current thread, and transforms and logs exceptions if the service is
called from the client tier.
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;
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.
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";
// 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);
}
}
@Inject
private Persistence persistence;
The proxy object factory is configured in spring.xml of the corresponding client block and contains service
names and interfaces.
For example, to call the sales_OrderService service from the web client in the sales application, 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 should be declared in the single remoteServices property in the map/entry
elements.
CUBA Studio automatically registers services in all client blocks of the project.
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;
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.
An entity can belong only to a single data store. You can display entities from different data stores on a
single UI screen, and DataManager will ensure they will be dispatched to appropriate data stores on save.
Depending on the entity type, DataManager selects a registered data store represented by an
implementation of the DataStore interface and delegates loading and saving entities to it. When you
control transactions in your code and work with entities via EntityManager, you have to specify explicitly
what data store to use. See the Persistence interface methods and @Transactional annotation parameters
for details.
The platform contains a single implementation of the DataStore interface called RdbmsStore. It is
designed to work with relational databases through the ORM layer. You can implement DataStore in your
project to provide integration, for example, with a non-relational database or an external system having
REST interface.
In any CUBA application, there is always the main data store which contains system and security entities
and where the users log in. When we mention a database in this manual, we always mean the main data
store if not explicitly stated otherwise. The main data store must be a relational database connected through
a JDBC data source. The main data source is located in JNDI and should have a name specified in the
cuba.dataSourceJndiName application property, which is jdbc/CubaDS by default.
Additional data store names should be specified in the cuba.additionalStores application property. If the
additional store is RdbmsStore, you should provide the following properties for it:
If you implement the DataStore interface in your project, specify the name of the implementation bean in
the cuba.storeImpl_{store_name} application property.
For example, if you need to work with two additional data stores: db1 (a PostgreSQL database) and mem1
(an in-memory storage implemented by some project bean), specify the following application properties in
the app.properties file of your core module:
CUBA Studio allows you to set up additional data stores on the Project properties > Advanced tab. It
automatically creates all required application properties and JDBC data sources, as well as maintains
additional persistence.xml files. After that, you can select a data store for an entity in the Data
store field of the entity designer. You will also be able to select a data store when using the Generate
model wizard for creation of new entities mapped to an existing database schema.
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.
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")
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.
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.
Shortcomings
Requires understanding of how ORM works.
Makes direct optimization of SQL and use of the DBMS specifics difficult.
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.
Unlike for DataManager, all local attributes are loaded regardless of what is specified in the view. In
EntityManager, the view affects only reference attributes.
@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;
}
}
See DataManager vs. EntityManager for information on differences between DataManager and
EntityManager.
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().
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.
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;
user.setName("newName");
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.
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.
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.
@between
Has the format @between(field_name, moment1, moment2, time_unit), where
The macro gets converted to the following expression in JPQL: field_name >= :moment1 and
field_name < :moment2
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.
@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
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];
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:
HSQLDB – String
PostgreSQL – UUID
Microsoft SQL Server – String
Oracle – String
MySQL – String
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.
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 Metadata metadata;
@Override
@Override
public void onBeforeUpdate(MyEntity entity, EntityManager entityManager) {
Foo foo = entityManager.find(Foo.class, entity.getFoo().getId());
...
}
}
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.
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
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.
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();
});
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.
value - data store name. If omitted, the main data store is assumed. For example:
@Transactional("db1")
public void doSomething() {
}
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.
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().
void methodB() {
Transaction tx = persistence.getTransaction();
try {
// (4) retrieving the same instance of EntityManager as methodA
EntityManager em = persistence.getEntityManager();
// (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
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();
void methodB() {
Transaction tx = persistence.createTransaction();
try {
// (4) creating a new instance of EntityManager,
// as this is a new transaction
EntityManager em = persistence.getEntityManager();
// (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");
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.
The default timeout can be defined using the cuba.defaultQueryTimeoutSec application property.
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:
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.
Query Cache
Query cache stores identifiers of entity instances returned by JPQL queries, so it naturally complements
the entity cache.
For example, if entity cache is enabled for an entity (say, sales$Customer), and you execute the query
select c from sales$Customer c where c.grade = :grade for the first time, the following
happens:
When you execute the same query with the same parameters the second time, the platform finds the
query in the query cache and loads entity instances from the entity cache by identifiers. No database
operations are needed.
Queries are not cached by default. You can specify that a query should be cached on different layers of
the application:
Using setCacheable() method of the Query interface when working with EntityManager.
Using setCacheable() method of the LoadContext.Query interface when working with
DataManager.
Using setCacheable() method of the CollectionDatasource interface or cacheable XML
attribute when working with datasources.
Use cacheable queries only if entity cache is enabled for the returned entity. Otherwise on every
query entity instances will be fetched from the database by their identifiers one by one.
Query cache is invalidated automatically when ORM performs creation, update or deletion of instances of
the corresponding entities. The invalidation works across the middleware cluster.
cuba.queryCache.enabled
cuba.queryCache.maxSize
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:
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.
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.
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 application component.
For Web Client the implementation is based on the Vaadin framework, for Desktop Client on the Java
Swing framework.
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.
Frame
Simple Screen
Lookup Screen
Edit Screen
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.
Rules for interaction between a screen and its enclosed frame are the following:
Screen commit also causes commits of modified datasources of the frame it uses.
The controller of a simple screen should be inherited from the AbstractWindow class.
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.
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:
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.
window attributes:
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.
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.
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
@Override
public void init(Map<String, Object> 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);
}
These parameters will be considered if they don’t conflict with the higher-priority parameters of the
window being opened. The latter can be set either in the getDialogOptions() method of screen controller
or in XML descriptor of the screen:
<dialogMode forceDialog="true" width="300" height="200" closeable="true" modal="true"/>
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
});
4.5.1.3.2. AbstractWindow
AbstractWindow is a subclass of AbstractFrame and defines the following methods:
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");
}
}
}
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) {
...
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.
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
}
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.
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.
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.
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:
Middleware services imported by Client
ComponentsFactory
WindowConfig
ExportDisplay
BackgroundWorker
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;
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 {
@Inject
protected Table<Customer> table;
@Inject
protected Companion companion;
@Override
public void init(Map<String, Object> params) {
if (companion != null) {
companion.initTable(table);
}
}
}
}
}
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.
There are three predefined screen agents in the platform: DESKTOP, TABLET, PHONE. They are defined by
the following classes respectively: DesktopScreenAgent, TabletScreenAgent, PhoneScreenAgent.
You can define your own agents by creating managed beans implementing the ScreenAgent interface.
A screen agent is specified for a screen in the screens.xml file. The value of the agent attribute should be
either one of the predefined constants listed above or a name of the custom bean implementing
ScreenAgent.
In Studio, an agent is specified on the Properties tab of the screen designer page.
Containers
Miscellaneous
4.5.2.1. Components
Component is the parent of all visual components. It contains basic attributes to identify a component and
place it within a screen.
Buttons
Button
PopupButton
LinkButton
Text
Label
Text inputs
TextField
PasswordField
MaskedField
TextArea
RichTextArea
SourceCodeEditor
Date inputs
DateField
DatePicker
TimeField
Selects
CheckBox
OptionsGroup
OptionsList
PickerField
LookupField
LookupPickerField
SearchPickerField
TwinColumn
Uploads
FileUploadField
FileMultiUploadField
Table
GroupTable
TreeTable
Tree
Others
Calendar
ColorPicker
FieldGroup
TokenList
PopupView
Filter
4.5.2.1.1. Button
A button performs an action when a user clicks on it.
LIVE DEMO
Component’s XML-name: button
Buttons can contain a caption, an icon, or both. The figure below shows different button types.
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.
If the disableOnClick attribute is set to true the button will be automatically disabled when clicked,
typically to prevent (accidental) extra clicks on a button. You can later return the button to the enabled state
by invoking the setEnabled(true) method.
The icon attribute defines icon location in theme catalog. Detailed information on recommended icon
placement is available in Themes.
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 invoke attribute is ignored if action attribute is set. The action attribute contains the name of action
corresponding to the button.
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 - disableOnClick - enable - icon - id - invoke - stylename - visible -
width
4.5.2.1.2. BulkEditor
BulkEditor is a component that enables changing attribute values for several
LIVE DEMO
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.
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 button next to the field. After that, the field will become non-editable. The field can be
unlocked by clicking the same button again.
</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
Attributes of bulkEditor
align - caption - description - enable - exclude - for - icon - id - openType - stylename - visible - width
4.5.2.1.3. Calendar
The Calendar component is intended to organize and display calendar events.
LIVE DEMO
The view mode is determined from the date range of the calendar, defined by the start date and the end
date. The default view is the weekly view, it is used for ranges up to seven days a week. For a single-day
view use the range within one date. Calendar will be shown in a monthly view when the date range is over
than one week (seven days) long.
Attributes of calendar:
endDateProperty - the name of an entity attribute that contains the end date.
descriptionProperty - the name of an entity attribute that contains the event description.
isAllDayProperty - the name of an entity attribute that determines if the event is all day long.
startDateProperty - the name of an entity attribute that contains the start date.
stylenameProperty - the name of an entity attribute that contains the event style name.
To display events in the calendar cells, you can add the events directly to the Calendar object using the
addEvent() method or use the CalendarEventProvider interface. An example of direct event adding:
@Inject
private Calendar calendar;
public void generateEvent(String caption, String description, Date start, Date end,
boolean isAllDay, String stylename) {
SimpleCalendarEvent calendarEvent = new SimpleCalendarEvent();
calendarEvent.setCaption(caption);
calendarEvent.setDescription(description);
calendarEvent.setStart(start);
calendarEvent.setEnd(end);
calendarEvent.setAllDay(isAllDay);
calendarEvent.setStyleName(stylename);
calendar.getEventProvider().addEvent(calendarEvent);
}
There are two data providers available: ListCalendarEventProvider (created by default) and
EntityCalendarEventProvider.
EntityCalendarEventProvider is filled with data directly from an entity fields. To be used for the
EntityCalendarEventProvider, an entity should at least have attributes for the event start date
(DateTime type), event end date (DateTime type) and event caption (String type). In the example below we
will assume that the datasource entity has all required attributes: eventCaption, eventDescription,
eventStartDate, eventEndDate, eventStylename, and will set their names as values for calendar
attributes:
<calendar id="calendar"
datasource="calendarEventsDs"
width="100%"
height="100%"
startDate="2016-10-01"
endDate="2016-10-31"
captionProperty="eventCaption"
descriptionProperty="eventDescription"
startDateProperty="eventStartDate"
endDateProperty="eventEndDate"
stylenameProperty="eventStylename"/>
The Calendar component supports several event listeners for user interaction with its elements, such as
date and week captions, date/time range selections, event dragging and event resizing. Navigation buttons
used to scroll forward and backward in time are also listened by the server. Below is the list of default
listeners:
Calendar events can be styled with CSS. To configure a style, create the style name and set the parameters
in the .scss-file. For example, let’s configure the background color of an event:
.v-calendar-event.event-green {
background-color: #c8f4c9;
color: #00e026;
}
Attributes of calendar
caption - captionProperty - colspan - datasource - description - descriptionProperty - endDateProperty -
endDate - height - icon - id - isAllDayProperty - rowspan - startDate - startDateProperty - stylename -
stylenameProperty - timeFormat - visible - width
4.5.2.1.4. CheckBox
CheckBox is a component with two states: selected or deselected.
LIVE DEMO
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.5. ColorPicker
ColorPicker is a field that allows a user to preview and select a color.
LIVE DEMO
Component returns a hexadecimal (HEX) value of the color as a string.
An example of a color picker with a caption retrieved from the localized messages pack:
<colorPicker id="colorPicker" caption="msg://colorPickerCaption"/>
The figure below shows an example of the color picker with the popup closed.
To create a color picker connected to data, use datasource and property attributes.
<dsContext>
<datasource id="carsDs" class="com.sample.sales.entity.Cars" view="_local"/>
</dsContext>
<layout>
<colorPicker id="colorPicker" datasource="carsDs" property="color"/>
Attributes of сolorPicker:
defaultCaptionEnabled - if set to true and buttonCaption is not set, displays HEX value as a
button caption.
historyVisible - determines the visibility of history of recently picked colors in the popup window.
You can determine visibility of the popup tabs using the following attributes:
Also, if you want to redefine the labels in popup, you can use caption attributes:
getValue() method of the component returns a string, containing a HEX code of the selected color.
Attributes of colorPicker
align - buttonCaption - cancelButtonCaption - caption - confirmButtonCaption - datasource -
defaultCaptionEnabled - editable - height - historyVisible - hsvVisible - icon - id - lookupAllCaption -
lookupBlueCaption - lookupGreenCaption - lookupRedCaption - popupCaption - rgbVisible - required -
stylename - swatchesTabCaption - swatchesVisible - visible - width
4.5.2.1.6. DateField
DateField is a field to display and enter date and time. It is an input field, inside
LIVE DEMO
which there is a button with a drop-down calendar. To the right, there is a time
field.
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"/>
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.
You can specify available dates by using rangeStart and rangeEnd attributes. If a range is set, all
dates outside the range will be disabled. You can set range dates in the "yyyy-MM-dd" format in XML or
programmatically by using corresponding setters.
<dateField id="dateField" rangeStart="2016-08-15" rangeEnd="2016-08-19"/>
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"/>
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 - rangeEnd - rangeStart - requiredMessage - resolution - visible - width
Elements of dateField
validator
4.5.2.1.7. DatePicker
DatePicker is a field to display and choose a date. It has the same view as the
LIVE DEMO
drop-down calendar in DateField.
To create a date picker 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>
<datePicker id="datePicker" 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
datePicker component; the name of the entity attribute which value should be displayed in the field is
specified in the property attribute.
You can specify available dates to select by using rangeStart and rangeEnd attributes. If you set
them, all the dates that are outside the range will be disabled.
<datePicker id="datePicker" rangeStart="2016-08-15" rangeEnd="2016-08-19"/>
Date accuracy can be defined using a resolution attribute. An attribute value should match the
DatePicker.Resolution enumeration − DAY, MONTH, YEAR. Default resolution is DAY.
<datePicker id="datePicker" resolution="MONTH"/>
Attributes of datePicker
align - caption - datasource - description - editable - enable - height - id - property - rangeEnd - rangeStart
- resolution - stylename - visible - width
4.5.2.1.8. Embedded
Embedded component is intended for displaying images and embedding arbitrary
LIVE DEMO
web pages into the application screens.
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.
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.9. FieldGroup
FieldGroup is intended for the joint display and editing of multiple entity
LIVE DEMO
attributes.
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).
link - if set to true, enables displaying a link to an entity editor instead of an entity picker field
(supported for Web Client only). Such behaviour may be required when the user should be able to
view the related entity, but should not change the relationship.
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.
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
optionsDatasource is
LookupPickerField
specified
Related Entity
PickerField
TextField
boolean CheckBox
time TimeField
Attributes of fieldGroup
align - border - caption - datasource - editable - enable - id - height - stylename - visible - width
Attributes of column
flex - id - width
Attributes of field
caption - captionProperty - cols - custom - datasource - dateFormat - description - editable - enable - id -
link - linkInvoke - linkScreen - linkScreenOpenType - mask - maxLength - optionsDatasource - required -
requiredMessage - resolution - rows - showSeconds - visible - width
Elements of field
validator
4.5.2.1.10. 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.
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
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 Administration → External 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 fileSizeLimit XML attribute (and the corresponding setFileSizeLimit() method) can be
used to set maximum allowed file size in bytes. This limit defines a maximum size for each file.
<multiUpload id="multiUploadField" fileSizeLimit="200000"/>
The dropZone XML attribute allows you to specify a BoxLayout to be used as a target for drag-and-
dropping files from outside of the browser. If the container style is not set, the selected container is
highlighted when a user drags files over the container, otherwise the dropZone is not visible.
See Loading and Displaying Images for more complex example of working with uploaded files.
Attributes of multiUpload
accept - align - caption - description - dropZone - enable - fileSizeLimit - height - icon - id -
permittedExtensions - stylename - visible - width
4.5.2.1.11. FileUploadField
The FileUploadField component allows a user to upload files to the server. The component can contain
a caption, a link to uploaded file, and two buttons: for uploading and for clearing the selected file. When the
upload button is clicked, a standard OS file picker window is shown, where the user can select a file. In
order to upload multiple files, use FileMultiUploadField.
configuration. An uploaded file is immediately stored in file storage and the corresponding
FileDescriptor instance is saved to the database.
You can use the component outside FieldGroup and connect it to a datasource. For example, here we
suppose that the personDs datasource contains an entity with the photo attribute which is a reference
to FileDescriptor:
<upload fileStoragePutMode="IMMEDIATE"
datasource="personDs"
property="photo"/>
You can also control the saving of file and FileDescriptor programmatically.
Declare the component in an XML screen descriptor:
<upload id="uploadField"
fileStoragePutMode="MANUAL"/>
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
is a persistent entity, which uniquely identifies an uploaded file and is used to download the file from
the system.
The fileStoragePutMode XML attribute defines how the file and the corresponding
FileDescriptor are stored. In the IMMEDIATE mode it is done right after uploading file to the
temporary storage of the client tier. In the MANUAL mode, you should do it programmatically in a
FileUploadSucceedListener. The IMMEDIATE mode is selected by default when
FileUploadField is used inside FieldGroup. Otherwise, the default mode is MANUAL.
The showClearButton XML attribute controls whether the clear button is visible. It is false by default.
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.
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 fileSizeLimit XML attribute (and the corresponding setFileSizeLimit() method) can be
used to set maximum allowed file size specified in bytes.
<upload id="uploadField" fileSizeLimit="2000"/>
The dropZone XML attribute allows you to specify a BoxLayout to be used as a target for drag-and-
dropping files from outside of the browser. The dropZone can cover the whole layout of a dialog window.
The selected container is highlighted when a user drags a file over the container, otherwise it is not
visible.
<layout spacing="true"
width="100%">
<vbox id="dropZone"
height="AUTO"
spacing="true">
<textField id="textField"
caption="Title"
width="100%"/>
<textArea id="textArea"
caption="Description"
width="100%"
rows="5"/>
<checkBox caption="Is reference document"
width="100%"/>
<upload id="upload"
dropZone="dropZone"
showClearButton="true"
showFileName="true"/>
</vbox>
<hbox spacing="true">
<button caption="mainMsg://actions.Apply"/>
<button caption="mainMsg://actions.Cancel"/>
</hbox>
</layout>
To make a dropZone static and display it permanently, assign the predefined cuba-static-
drop-zone style to its container. In this case the container should be empty with only the label
component inside:
Unresolved directive in gui_vcl.adoc - include::../../source/gui_vcl
/dropZone_static.xml[]
See Loading and Displaying Images for more complex example of working with uploaded files.
Attributes of upload
accept - align - caption - clearButtonCaption - clearButtonDescription - clearButtonIcon - datasource -
description - dropZone - editable - enable - fileSizeLimit - fileStoragePutMode - height - icon - id -
permittedExtensions - property - showClearButton - stylename - uploadButtonCaption -
uploadButtonDescription - uploadButtonIcon - visible - width
4.5.2.1.12. Filter
The Filter is a versatile tool for filtering lists of entities extracted from a
LIVE DEMO
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
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:
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
Selected conditions are displayed at the top of the filter panel. The 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:
After that, the filter will be saved and will appear in the drop-down menu of the Search button.
The filter settings popup button provides the list of options for filter management:
The Edit option opens the filter editor, allowing advanced configuration of the current filter:
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. This operation requires a specific permission called CUBA > Filter >
Create/modify global filters.
The filter conditions are contained in the tree. They can be added using the Add button, swapped using
/ 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
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:
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:
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"
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:
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.
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.
columnsCount - 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 - columnsCount - 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.
System-wide parameters
The following application properties affect filter behavior:
cuba.gui.genericFilterManualApplyRequired − disables automatic applying of the filter (i.e., data
loading) when the screen is opened. See also manualApplyRequired filter attribute.
cuba.gui.genericFilterChecking − enables the check that at least one condition is filled before applying
the filter.
cuba.gui.genericFilterControlsLayout − defines an internal layout of the filter controls.
cuba.allowQueryFromSelected enables switching off sequential filters application mechanism.
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.
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.
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 button.
The sequential filters implementation is based on the ability of DataManager to run sequential queries.
The Filter interface provides methods for reading and writing of filter parameter values in a screen
controller:
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.
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.
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.13. GroupTable
GroupTable component is a table with an ability to group information dynamically
LIVE DEMO
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 element of the table header. Grouped values can be expanded
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.
If multiselect attribute is true, the click to the group row holding down the Ctrl key will expand the
group (if collapsed) and set the selection to all rows of this group. The converse is not true: if the whole
group is selected, Ctrl+click will not deselect all the group. You still can deselect certain rows using the
common Ctrl key behaviour.
Attributes of groupTable
align - aggregatable - aggregationStyle - columnControlVisible - contextMenuEnabled - editable - enable -
height - id - multiLineCells - multiselect - presentations - reorderingAllowed - showTotalAggregation -
sortable - stylename - visible - width
Elements of groupTable
actions - buttonsPanel - columns - rows - rowsCount
Elements of columns
column - group
Attributes of column
align - caption - captionProperty - collapsed - dateFormat - editable - groupAllowed - id - link - linkInvoke -
linkScreen - linkScreenOpenType - maxTextLength - optionsDatasource - resolution - sortable - visible -
width
Elements of column
aggregation - formatter
Attributes of rows
datasource
4.5.2.1.14. Label
Label is a text component that displays static text or value of an entity attribute.
LIVE DEMO
XML name of the component: label
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"/>
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;
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
4.5.2.1.15. Link
Link is a hyperlink, which enables uniform opening of external web resources for
LIVE DEMO
the Web and Desktop client.
link attributes:
target – sets the web page opening mode for the Web Client, the same as the target attribute of the
<a> HTML element.
Attributes of link
align - caption - description - enable - icon - id - stylename - url - target - visible - width
4.5.2.1.16. LinkButton
LinkButton is a button that looks like a hyperlink.
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.
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.17. 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
LIVE DEMO
values.
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
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 nullOptionVisible XML attribute sets visibility for the null option in the drop-down list. It allows
you to make LookupField not required but still without the null option.
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.
Attributes of lookupField
align - caption - captionProperty - datasource - description - editable - enable - filterMode - height - icon -
id - inputPrompt - newOptionAllowed - newOptionHandler - nullName - nullOptionVisible -
optionsDatasource - pageLength - property - required - requiredMessage - stylename - textInputAllowed -
visible - width
Elements of lookupField
validator
4.5.2.1.18. LookupPickerField
The LookupPickerField component enables to display an entity instance in a
LIVE DEMO
text field, select an instance in a drop-down list and perform actions by pressing
buttons on the right.
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 and open 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"/>
Attributes of lookupPickerField
align - caption - captionProperty - datasource - description - editable - enable - filterMode - height - icon -
id - inputPrompt - metaClass - newOptionAllowed - newOptionHandler - nullName - optionsDatasource -
pageLength - property - required - requiredMessage - stylename - visible - width
Elements of lookupPickerField
actions - validator
4.5.2.1.19. 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.
LIVE DEMO
XML name of the component: maskedField.
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;
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.20. 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
LIVE DEMO
multiple values.
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:
The list of component options can be specified arbitrarily using the setOptionsList(),
setOptionsMap() and setOptionsEnum() methods, or using an optionsDatasource attribute.
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);
}
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);
}
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.
@Override
public void init(Map<String, Object> params) {
roleTypesField.setOptionsList(Arrays.asList(RoleType.values()));
}
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
Attributes of optionsGroup
align - caption - captionProperty - datasource - description - editable - enable - icon - id - multiselect -
height - optionsDatasource - orientation - property - required - requiredMessage - stylename - visible -
width
Elements of optionsGroup
validator
4.5.2.1.21. OptionsList
OptionsList is a variation of the OptionsGroup component which represents the list of options as a
vertical scrollable list. If multi-select is enabled, multiple items can be selected by holding the Ctrl key while
clicking, or as a range by holding the Shift key.
The only difference in API between OptionsList and OptionsGroup is that OptionsList has no
orientation attribute.
Attributes of optionsList
align - caption - captionProperty - datasource - description - editable - enable - icon - id - multiselect -
height - optionsDatasource - property - required - requiredMessage - stylename - visible - width
Elements of optionsList
validator
4.5.2.1.22. PasswordField
This is a text field that displays echo characters instead of those entered by a user.
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;
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.23. PickerField
PickerField displays an entity instance in a text field and performs actions
LIVE DEMO
when a user clicks buttons on the right.
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:
@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:
@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.
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.24. PopupButton
This is a button with a drop-down list of actions.
LIVE DEMO
PopupButton can contain text or icon (or both). The figure below shows different types of buttons.
</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.25. PopupView
PopupView is a component that allows you to open a popup with a container. The
LIVE DEMO
popup can be opened by clicking on minimized value or programmatically. It can
be closed by mouse out or by clicking on outside area.
An example of a PopupView with minimized value retrieved from a localized message pack:
<popupView id="popupView"
minimizedValue="msg://minimizedValue"
caption="PopupView caption">
<vbox width="60px" height="40px">
<label value="Content" align="MIDDLE_CENTER"/>
</vbox>
</popupView>
The inner content of the PopupView should be a container, for example BoxLayout.
PopupView methods:
@Inject
private PopupView popupView;
@Override
public void init(Map<String, Object> params) {
popupView.setPopupVisible(true);
}
@Override
public void init(Map<String, Object> params) {
popupView.setMinimizedValue("Hello world!");
}
@Override
public void init(Map<String, Object> params) {
popupView.addPopupVisibilityListener(event ->
showNotification(event.isPopupVisible() ? "The popup is visible" : "The popup
is hidden",
NotificationType.HUMANIZED));
}
PopupView attributes:
If the hideOnMouseOut attribute is set to false, popup container will close after click on outside area.
Attributes of popupView
caption - captionAsHtml - description - height - hideOnMouseOut - icon - id - minimizedValue - stylename
- visible - width
4.5.2.1.26. ProgressBar
The ProgressBar component is designed to display the progress of a long
LIVE DEMO
process.
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;
@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);
}
};
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.27. RelatedEntities
RelatedEntities component is a popup button with a drop-down list of classes
LIVE DEMO
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.
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.
<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 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.
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.
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.28. RichTextArea
This is a text area to display and enter formatted text.
LIVE DEMO
XML name of the component: richTextArea
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.
Attributes of richTextArea
align - caption - datasource - description - editable - enable - height - icon - id - property - required -
requiredMessage - stylename - visible - width
4.5.2.1.29. SearchPickerField
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 and open actions.
Attributes of searchPickerField
align - caption - captionProperty - datasource - description - editable - enable - filterMode - height - icon -
id - inputPrompt - metaClass - minSearchStringLength - newOptionAllowed - newOptionHandler -
nullName - optionsDatasource - property - required - requiredMessage - stylename - visible - width
Elements of searchPickerField
actions - validator
4.5.2.1.30. SourceCodeEditor
SourceCodeEditor is a component to display and enter source code. It is
LIVE DEMO
multi-line text area featured with code highlighting and optional print margin and
gutter with line numbers.
Basically, SourceCodeEditor mostly replicates the functionality of the TextField component and has the
following specific attributes:
if handleTabKey is true, the Tab button on the keyboard is handled to indent lines, when false, it is
used to advance the cursor or focus to the next tab stop. This attribute should be set when the screen is
initialized and will not work if changed at a runtime.
mode provides the list of languages supported for the syntax highlight. This list is defined in the Mode
enumeration of the SourceCodeEditor interface and includes the following languages: Java, HTML,
XML, Groovy, SQL, JavaScript, Properties, and Text with no highlight.
printMargin attribute sets if the printing edge line should be displayed or hidden.
showGutter is used to hide or show the left gutter with line numbers.
XML-descriptor:
<hbox spacing="true">
<checkBox id="highlightActiveLineCheck" align="BOTTOM_LEFT" caption="Highlight Active
Line"/>
<checkBox id="printMarginCheck" align="BOTTOM_LEFT" caption="Print Margin"/>
<checkBox id="showGutterCheck" align="BOTTOM_LEFT" caption="Show Gutter"/>
<lookupField id="modeField" align="BOTTOM_LEFT" caption="Mode" required="true"/>
</hbox>
<sourceCodeEditor id="simpleCodeEditor" width="100%"/>
The controller:
@Inject
private LookupField modeField;
@Inject
private SourceCodeEditor simpleCodeEditor;
@Inject
private CheckBox highlightActiveLineCheck;
@Inject
private CheckBox printMarginCheck;
@Inject
private CheckBox showGutterCheck;
@Override
public void init(Map<String, Object> params) {
highlightActiveLineCheck.setValue(simpleCodeEditor.isHighlightActiveLine());
highlightActiveLineCheck.addValueChangeListener(e ->
simpleCodeEditor.setHighlightActiveLine(Boolean.TRUE.equals(e.getValue())));
printMarginCheck.setValue(simpleCodeEditor.isShowPrintMargin());
printMarginCheck.addValueChangeListener(e ->
simpleCodeEditor.setShowPrintMargin(Boolean.TRUE.equals(e.getValue())));
showGutterCheck.setValue(simpleCodeEditor.isShowGutter());
showGutterCheck.addValueChangeListener(e ->
simpleCodeEditor.setShowGutter(Boolean.TRUE.equals(e.getValue())));
modeField.setOptionsMap(modes);
modeField.setValue(SourceCodeEditor.Mode.Text);
modeField.addValueChangeListener(e -> simpleCodeEditor.setMode((SourceCodeEditor.Mode)
e.getValue()));
}
SourceCodeEditor can also support code autocomplete provided by the Suggester class. To activate
word completion, the setSuggester method should be invoked, for example:
@Inject
private SourceCodeEditor suggesterCodeEditor;
@Inject
private CollectionDatasource<User, UUID> usersDs;
@Override
public void init(Map<String, Object> params) {
suggesterCodeEditor.setSuggester((source, text, cursorPosition) -> {
List<Suggestion> suggestions = new ArrayList<>();
usersDs.refresh();
for (User user : usersDs.getItems()) {
suggestions.add(new Suggestion(source, user.getLogin(), user.getName(), null,
-1, -1));
}
return suggestions;
});
}
The result:
Attributes of sourceCodeEditor
align - caption - colspan - datasource - description - editable - enable - handleTabKey - height -
highlightActiveLine - icon - id - mode - printMargin - property - required - requiredMessage - rowspan -
showGutter - stylename - visible - width
4.5.2.1.31. Table
The Table component presents information in a table view, sorts data, manages
LIVE DEMO
table columns and headers and invokes actions for selected rows.
The component is implemented for both Web Client and Desktop Client.
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;
}
}
});
}
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 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:
link - if set to true, enables displaying a link to an entity editor in a table column (supported for
Web Client only). The link attribute may be set to true for primitive type columns, too; in this case,
the main entity editor will be opened. This approach may be used to simplify navigation: the users will
be able to open entity editors simply by clicking on some key attributes.
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).
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 / 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 . 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 button in the right part of the table header. Currently displayed columns are
marked with checkmarks in the menu.
If the reorderingAllowed attribute is set to false, users cannot change columns order by dragging
them with a mouse.
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. By default, only numeric data types are supported in aggregated columns,
such as Integer, Double, Long and BigDecimal. 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;
}
}
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 Date – DateField, 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.
@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");
requestFocus() method allows you to set focus on the concrete editable field of a certain row. It takes
two parameters: an entity instance which identifies the row and the column identifier. Example of
requesting a focus:
table.requestFocus(item, "count");
scrollTo() method allows you to scroll table to the concrete row. It takes one parameter: an entity
instance identifying the row.
Example of scrolling:
table.scrollTo(item);
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.
Attributes of table
align - aggregatable - aggregationStyle - columnControlVisible - columnHeaderVisible -
contextMenuEnabled - editable - enable - height - id - multiLineCells - multiselect - presentations -
reorderingAllowed - showSelection - sortable - stylename - visible - width
Elements of table
actions - buttonsPanel - columns - rows - rowsCount
Attributes of column
align - caption - captionProperty - collapsed - dateFormat - editable - id - link - linkInvoke - linkScreen -
linkScreenOpenType - maxTextLength - optionsDatasource - resolution - sortable - visible - width
Elements of column
aggregation - formatter
Attributes of aggregation
type - strategyClass
Attributes of rows
datasource
4.5.2.1.32. TextArea
TextArea is a multi-line text editor field.
LIVE DEMO
XML-name of the component: textArea
TextArea component is implemented for both Web Client and Desktop Client.
TextArea mostly replicates the functionality of the TextField component and has the following specific
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"/>
Attributes of textArea
align - caption - cols - datasource - datatype - description - editable - enable - height - icon - id -
maxLength - property - required - requiredMessage - resizable - rows - stylename - trim - visible - width -
wordwrap
4.5.2.1.33. TextField
TextField is a component for text editing. It can be used both for working with
LIVE DEMO
entity attributes and entering/displaying arbitrary textual information.
An example of a text field with a caption retrieved from the localized messages pack:
<textField id="nameField" caption="msg://name"/>
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");
}
}
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:
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.
Attributes of textField
align - caption - datasource - datatype - description - editable - enable - height - icon - id - inputPrompt -
maxLength - property - required - requiredMessage - stylename - trim - visible - width
Elements of textField
validator
4.5.2.1.34. TimeField
TimeField is a field for displaying and entering time.
LIVE DEMO
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"/>
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.35. TokenList
TokenList component offers a simplified way of working with lists: instance
LIVE DEMO
names are listed vertically or horizontally, adding is done using drop-down list,
removal – using the buttons located near each instance.
The component is implemented for both Web Client and Desktop Client.
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.
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:
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. Clicking the Clear button will remove all elements from
the TokenList component datasource.
tokenList elements:
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.
addButton – descriptor of the button for adding items. Can contain caption and icon attributes.
Attributes of tokenList
align - caption - captionProperty - clearEnabled - datasource - description - editable - enable - height -
icon - id - inline - position - simple - stylename - visible - width
Elements of tokenList
addButton - lookup
Attributes of lookup
captionProperty - filterMode - lookup - lookupScreen - multiselect - openType - optionsDatasource
Attributes of button
caption - icon
4.5.2.1.36. Tree
The Tree component is intended to display hierarchical structures represented by
LIVE DEMO
entities referencing themselves.
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.
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.37. TreeTable
TreeTable component is a hierarchical table displaying a tree-like structure in
LIVE DEMO
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.
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.
treeTable attributes
align - aggregatable - aggregationStyle - columnControlVisible - contextMenuEnabled - editable - enable -
height - id - multiLineCells - multiselect - presentations - reorderingAllowed - sortable - stylename - visible
- width
treeTable elements
actions - buttonsPanel - columns - rows - rowsCount
column attributes
align - caption - captionProperty - collapsed - dateFormat - editable - id - link - linkInvoke - linkScreen -
linkScreenOpenType - maxTextLength - optionsDatasource - resolution - sortable - visible - width
column elements
aggregation - formatter
rows attribute
datasource
4.5.2.1.38. TwinColumn
TwinColumn is a twin list component for multiple items selection. The left part of
LIVE DEMO
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.
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.
leftColumnCaption and rightColumnCaption attributes can be used to set captions for the columns.
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.
Attributes of twinColumn
Elements of twinColumn
validator
4.5.2.2. Containers
Accordion
BoxLayout
ButtonsPanel
CssLayout
Frame
GridLayout
GroupBoxLayout
HtmlBoxLayout
ScrollBoxLayout
SplitPanel
TabSheet
4.5.2.2.1. Accordion
Accordion is a container for collapsible content that allows you to toggle
LIVE DEMO
between hiding and showing large amount of content. The Accordion container
is implemented for Web Client.
The accordion component should contain nested tab elements describing tabs. Each tab is a container
with a vertical components layout similar to vbox. Accordion container can be used if the application page is
limited in space or the tab title is too long to be displayed in the TabSheet. Accordion is featured with a
smooth transition animation.
id – tab identifier. Please note that tabs are not components and their IDs are used only within the
Accordion in order to work with tabs from the controller.
caption – tab caption.
icon - defines icon location in theme catalog. Detailed information on recommended icon placement is
available in Themes.
@Override
public void init(Map<String, Object> params) {
accordion.addListener(new Accordion.TabChangeListener() {
@Override
public void tabChanged(Accordion.Tab newTab) {
if ("tabCambridge".equals(newTab.getName())) {
initCambridgeTab();
}
}
});
}
By default, tabs are not lazy, which means that all their content is loaded when a screen is opened.
An accordion tab can contain any other visual container, such as table, grid etc:
<accordion id="accordion" height="100%" width="100%" enable="true">
<tab id="tabNY" caption="msg://tabNY" margin="true" spacing="true">
<table id="nYTable" width="100%">
<columns>
<column id="borough"/>
<column id="county"/>
<column id="population"/>
<column id="square"/>
</columns>
<rows datasource="newYorkDs"/>
</table>
</tab>
</accordion>
Attributes of accordion
colspan - enable - height - id - rowspan - stylename - visible - width
Attributes of tab
caption - description - enable - expand - icon - id - lazy - margin - spacing - stylename - visible
4.5.2.2.2. BoxLayout
BoxLayout is a container with sequential placement of components.
vbox − components are placed vertically. vbox has 100% width by default.
LIVE DEMO
optionsDatasource="customersDs"/>
<textField datasource="orderDs" property="amount"/>
</flowBox>
4.5.2.2.3. ButtonsPanel
ButtonsPanel is a container that streamlines the use and placement of the
LIVE DEMO
components (usually, buttons) for data management in a 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.
Attributes of buttonsPanel
align - alwaysVisible - enable - expand - height - id - stylename - visible - width
4.5.2.2.4. CssLayout
CssLayout is a container that enables full control over placement and styling of
LIVE DEMO
enclosed components using CSS.
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>
@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.5. Frame
frame element is designed to include frames into a screen.
LIVE DEMO
Attributes:
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.6. GridLayout
GridLayout container places components on a grid.
LIVE DEMO
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.
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="Name 1"/>
<textField/>
<label value="Name 2"/>
<textField/>
</row>
<row>
<label value="Name 3"/>
<textField colspan="3" width="100%"/>
</row>
</rows>
</grid>
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.7. GroupBoxLayout
GroupBoxLayout is a container that enables framing the embedded components
LIVE DEMO
and setting a universal header for them. Additionally, it can collapse content.
groupBox attributes:
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 / 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:
showAsPanel – if set to true, the component will look like Vaadin Panel. The default value is false.
Attributes of groupBox
align - caption - collapsable - collapsed - expand - height - id - orientation - showAsPanel - spacing -
stylename - width
4.5.2.2.8. HtmlBoxLayout
HtmlBoxLayout is a container that enables you to define locations of
LIVE DEMO
components in an HTML template. The layout template is included in a theme.
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 an 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.
<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.
@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.9. ScrollBoxLayout
ScrollBoxLayout − a container that supports content scrolling.
LIVE DEMO
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.
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.10. SplitPanel
SplitPanel − a container divided into two areas by a movable separator.
LIVE DEMO
split container must contain two nested containers or components. They will be displayed on both sides
of the separator.
split attributes:
minSplitPosition, maxSplitPosition - defines a range of the available position of the split which
can be set in pixels or percents.
For example, you can restrict moving the splitter between 100 and 300 pixels from the left side of the
component as follows:
<split id="splitPanel" maxSplitPosition="300px" minSplitPosition="100px" width="100%"
height="100%">
<vbox margin="true" spacing="true">
<button caption="Button 1"/>
<button caption="Button 2"/>
</vbox>
<vbox margin="true" spacing="true">
<button caption="Button 4"/>
<button caption="Button 5"/>
</vbox>
</split>
If you want to set the range programmatically, specify a unit of value with Component.UNITS_PIXELS or
Component.UNITS_PERCENTAGE:
splitPanel.setMinSplitPosition(100, Component.UNITS_PIXELS);
splitPanel.setMaxSplitPosition(300, Component.UNITS_PIXELS);
pos – an integer number defining percentage of the first component area compared to the second one.
For example, pos="30" means that the areas ration is 30/70. By default the areas are divided 50/50.
reversePosition - indicates that the pos attribute specifies a position of the splitter from the opposite
side of the component.
If the locked attribute is set to true, users are unable to change the separator position.
SplitPanel methods:
You can get a position of the splitter using the getSplitPosition() method.
If you need to get a unit of splitter position, use getSplitPositionUnit() method. It will return
Component.UNITS_PIXELS or Component.UNITS_PERCENTAGE.
isSplitPositionReversed() returns true if position is set from the opposite side of the
component.
Attributes of split
align - height - id - locked - minSplitPosition - maxSplitPosition - orientation - pos - reversePosition - width
4.5.2.2.11. TabSheet
TabSheet container is a tabbed panel. The panel shows content of one tab at a
LIVE DEMO
time.
The tabSheet component should contain nested tab elements describing tabs. Each tab is a container
with a vertical components layout similar to vbox.
id – tab identifier. Please note that tabs are not components and their IDs are used only within a
TabSheet in order to work with tabs from the controller.
caption – tab caption.
icon - defines icon location in theme catalog. Applicable only for the Web Client. Detailed information on
recommended icon placement is available in Themes.
@Override
public void init(Map<String, Object> params) {
tabsheet.addListener(
new TabSheet.TabChangeListener() {
@Override
public void tabChanged(TabSheet.Tab newTab) {
if ("detailsTab".equals(newTab.getName())){
initDetails();
} else if ("historyTab".equals(newTab.getName())){
initHistory();
}
}
}
);
}
By default, tabs are not lazy, which means that all their content is loaded when a screen is opened.
detachable – when it is true, a tab can be detached to a separate window in a screen desktop
implementation. It enables, for example, different parts of the application UI to be located on different
monitors. A detachable tab has a special button in its header:
Attributes of tabSheet
height - id - stylename - visible - width
Attributes of tab
caption - detachable - enable - expand - margin - icon - id - lazy - spacing - stylename - visible
4.5.2.3. Miscellaneous
This section describes different elements of the generic user interface that are related to visual components.
4.5.2.3.1. Formatter
Formatter should be used with read-only components, such as Label, Table column and similar.
Editable components values, for example, TextField, should be formatted using the Datatype
mechanism.
If formatter’s constructor class has a org.dom4j.Element, parameter, then it will receive an XML
element, describing this formatter. This can be used to parameterize a formatter instance. For example,
using a formatted string. Particularly, DateFormatter and NumberFormatter classes in the platform can
take the format string from the format attribute. Example of using the component:
<column id="date">
<formatter class="com.haulmont.cuba.gui.components.formatters.DateFormatter"
format="yyyy-MM-dd HH:mm:ss"/>
</column>
Additionally, DateFormatter class also recognizes a type attribute, which can have a DATE or DATETIME
value. In this case, formatting is done using the Datatype mechanism using a dateFormat or a
dateTimeFormat string respectively. For example:
<column id="endDate">
<formatter class="com.haulmont.cuba.gui.components.formatters.DateFormatter"
type="DATE"/>
</column>
If a formatter is implemented as an internal class, it should be declared with a static modifier and its
name should be separated by "$" for loading, for example:
<formatter class="com.sample.sales.gui.OrderBrowse$CurrencyFormatter"/>
Formatter can be assigned to a component not only using a screen XML-descriptor , but also
programmatically – by submitting a formatter instance into a setFormatter() component.
An example of declaring a custom formatter and using it to format values in a table column:
public class CurrencyFormatter implements Formatter<BigDecimal> {
@Override
public String format(BigDecimal value) {
return currentCurrency.format(value);
}
}
4.5.2.3.2. Presentations
The mechanism of presentations allows users to manage table settings.
Users can:
Save presentations with unique names. Table settings are automatically saved in an active presentation.
Edit and remove presentations.
Switch between presentations.
Set up a default presentation, which will be applied on the screen opening.
Create global presentations, available to all users. In order to create, change or remove global
presentations, a user should have cuba.gui.presentations.global security permission.
Table
GroupTable
TreeTable
4.5.2.3.3. Timer
Timer is a non-visual component allowing certain screen controller code to be run at specified time intervals.
The timer works in a thread that handles user interface events, therefore it can update screen components.
Timer stops working when a screen it was created for gets closed.
The component is implemented for the Web Client and the Desktop Client. For the web client, timer
implementation is based on polling from web-browser, for the desktop client it is based on
javax.swing.Timer.
The main approach for creating timers is by declaring them in a screen XML-descriptor – in the timers
element which is located between dsContext and layout elements.
@Inject
private CollectionDatasource bookInstanceDs;
A timer can be injected into a controller field, or acquired using the Window.getTimer() method. Timer
execution can be controlled using the timer’s start() and stop() methods. For an already active timer,
start() invocation will be ignored. After stopping the timer using stop() method, it can be started again
with start().
Example of defining a timer in an XML descriptor and using timer listeners in a controller:
<timers>
<timer id="helloTimer" delay="5000"/>
</timers>
@Inject
private Timer helloTimer;
@Override
public void init(Map<String, Object> params) {
// add execution handler
helloTimer.addActionListener(timer -> {
showNotification("Hello", NotificationType.HUMANIZED);
});
// add stop listener
helloTimer.addStopListener(timer -> {
showNotification("Timer is stopped", NotificationType.HUMANIZED);
});
// start the timer
helloTimer.start();
}
@Override
public void init(Map<String, Object> params) {
// create timer
Timer helloTimer = componentsFactory.createTimer();
// add timer to the screen
addTimer(helloTimer);
// set timer parameters
helloTimer.setDelay(5000);
helloTimer.setRepeating(true);
4.5.2.3.4. Validator
Validator is designed to check values entered into visual components.
Validation and input type checking should be differentiated. If a given component (e.g. TextField) data
type is set to anything different than string (this can happen when binding to an entity attribute or
setting datatype), then the component will not allow the user to enter a value that does not comply
with this data type. When the component loses focus or when the user presses Enter, the component
will show the previous correct value.
On the other hand, validation does not act immediately on data entry or focus loss, but rather when
the component’s validate() method is invoked. It means that the component (and the entity
attribute that it is linked to) may temporarily contain a value, which does not comply with the conditions
of validation. It should not be a problem because the validated fields are typically located in edit
screens, which automatically invoke validation for all their fields before commit. If the component is
located not in an edit screen, its validate() method should be invoked explicitly in the screen
controller.
In a screen XML-descriptor, a component validator can be defined in a nested validator elements. The
validator element can have the following attributes:
Groovy validator scripts and standard classes of Java validators, located in the
com.haulmont.cuba.gui.components.validators package support message attribute − a
message displayed to a user when validation fails. The attribute value should contain either a message or a
message key from the messages pack of the current screen. For example:
<validator class="com.haulmont.cuba.gui.components.validators.PatternValidator"
message="msg://validationError"
pattern="\d{3}"/>
# messages.properties
validationError = Input error
If the value of the script attribute is not set and the validator element itself does not contain text
with a Groovy expression, then the system will use a class defined in the class attribute as a validator.
If the validator element contains text, it will be used as a Groovy expression and executed using
Scripting.
Otherwise, the system will use Scripting to run a Groovy script defined in the script attribute.
The value variable will be passed to a Groovy expression or script. It contains the value entered into a
visual component. The expression or the script should return a boolean value: true − valid, false − not
valid.
If a Java class is being used as a validator, it should have a default constructor without parameters or a
constructor with the following set of parameters:
org.dom4j.Element, String – this constructor will receive the validator XML-element and the
message pack name of the screen.
org.dom4j.Element – this constructor will receive the validator XML-element.
If the validator is implemented as an internal class, it should be declared with a static modifier and
its name should be separated by "$", for example:
<validator class="com.sample.sales.gui.AddressEdit$ZipValidator"/>
The platform contains the set of implementations for the most frequently used validators (see
com.haulmont.cuba.gui.components.validators package), which can be used in your project:
DateValidator
DoubleValidator
EmailValidator
IntegerValidator
LongValidator
PatternValidator
RangeValidator
ScriptValidator
StringValidator
A validator class can be assigned to a component not only using a screen XML-descriptor, but also
programmatically – by submitting a validator instance into the component’s addValidator() method.
Example of using a zip code validator and a standard pattern validator for fields of a FieldGroup component:
<fieldGroup>
<field id="zip" required="true">
<validator class="com.company.sample.gui.ZipValidator"/>
</field>
<field id="imei">
<validator class="com.haulmont.cuba.gui.components.validators.PatternValidator"
pattern="\d{15}"
message="IMEI validation failed"/>
</field>
</fieldGroup>
TOP_RIGHT
TOP_LEFT
TOP_CENTER
MIDDLE_RIGHT
MIDDLE_LEFT
MIDDLE_CENTER
BOTTOM_RIGHT
BOTTOM_LEFT
BOTTOM_CENTER
caption
Sets the component’s caption.
The attribute value can either be a message or a key in a message pack. In case of a key, the value
should begin with the msg:// prefix.
A short key – in this case the message will be searched in the package of the current screen:
caption="msg://infoFieldCaption"
captionProperty
Defines the name of an entity attribute which is displayed by a component. captionProperty can only
be used for entities contained in a datasource (for example, defined by the optionsDatasource property of
the LookupField component).
colspan
Sets the number of grid columns that the component should occupy (default is 1).
This attribute can be defined for any component located immediately within a GridLayout container.
datasource
Sets a data source defined in the dsContext section of the screen XML descriptor.
When setting the datasource attribute for a component implementing the DatasourceComponent
interface, the property attribute should also be set.
description
Defines a hint which is displayed in a popup when a user hover mouse over the component.
editable
Indicates that the component’s content can be edited (do not confuse with enable).
enable
Defines the component’s enabled/disabled state.
If a component is disabled, it does not accept input focus. Disabling a container disables all of its
components as well. Possible values are true, false. By default all components are enabled.
expand
Defines a component within the container that should be expanded to use all available space in the
direction of component placement. For a container with vertical placement, this attribute sets 100% height
to a component; for the containers with horizontal placement - 100% width. Additionally, resizing a
container will resize the expanded component.
height
Sets the component’s height. Can be set in pixels or in percents of the parent container height. For
example: 100px, 100%, 50. If it is specified without units, pixels are assumed.
Setting a value in % means that the component will occupy the corresponding height within an area
When set to AUTO or -1px, a default value will be used for the component height. For a container, default
height is defined by the content: it is the sum of the heights of all nested components.
icon
Sets a component icon.
The attribute value should contain a path to an icon file relative to the themes folder. For example:
icon="icons/create.png"
If different icons should be displayed depending on the user’s language, you can set paths to the icons in
the message pack and specify a message key in the icon attribute, for example:
icon="msg://addIcon"
Font elements of Font Awesome can be used instead of files in web client with Halo theme (or derived
from it). For this, specify the name of the required constant of the com.vaadin.server.FontAwesome
class in the icon property with the font-icon: prefix, for example:
icon="font-icon:BOOK"
id
Sets an identifier of the component.
It is recommended to create identifiers according to the rules for Java-identifiers and use camelСase, for
example: userGrid, filterPanel. The id attribute can be specified for any component and should be
unique within a screen.
inputPrompt
Defines a string which is displayed in the field when its value is null.
margin
Defines indentation between the outer borders and the container content.
nullName
Selection of the option defined in the nullName attribute is equal to setting the null value to the
component.
property="customer"
nullName="(none)"
optionsDatasource="customersDs" width="200px"/>
customerLookupField.setNullOption("<null>");
openType
Defines how a related screen will be opened. Corresponds to the WindowManager.OpenType
enumeration with the values NEW_TAB, THIS_TAB, NEW_WINDOW, DIALOG. Default value is THIS_TAB.
optionsDatasource
Sets the name of a data source which contains a list of options.
property
Sets the name of an entity attribute which value will be displayed and edited by this visual component.
required
Indicates that this field requires a value.
requiredMessage
Used together with the required attribute. It sets a message that will be displayed to a user when the
component has no value.
The attribute can contain a message or a key from a message pack, for example:
requiredMessage="msg://infoTextField.requiredMessage"
rowspan
Sets the number of grid lines that the component should occupy (default is 1).
This attribute can be set for any component located immediately within a GridLayout container.
spacing
Sets spacing between components within a container.
stylename
Defines a style name for a component. See Themes for details.
visible
If a container is invisible all its components are invisible. By default all components are visible.
width
Defines component’s width.
The value can be set in pixels or in percents of the width of the parent container. For example: 100px,
100%, 50. If specified without units, pixels are assumed. Setting a value in % means that the component
will occupy the corresponding width within an area provided by the parent container.
When set to AUTO or -1px, a default value will be used for a component width. For a container, the
default width is defined by the content: it is the sum of the widths of all nested components.
4.5.3. Datasources
Datasources provide data to data-aware components.
Visual components themselves do not access Middleware: they get entity instances from linked
datasources. Furthermore, one datasource can work with multiple visual components if they need the same
instance or set of instances.
When a user changes a value in the component, the new value is set for the entity attribute in the
datasource.
When the entity attribute is modified in the code, the new value is set and displayed in the visual
component.
User input can be monitored both by datasource listeners and value listeners on the component – they
are notified sequentially.
To read or write the value of an attribute in the application code, it is recommended to use the
datasource, rather than the component. Below is an example of reading the attribute:
@Inject
private FieldGroup fieldGroup;
@Inject
private Datasource<Order> orderDs;
As you can see, working with entity attribute values through a component requires type casting and, in
case of FieldGroup, specifying the attribute name as a string. At the same time, if the instance is
obtained from the datasource via the getItem() method, the values of attributes can be read and
modified directly.
Datasources also track changes in entities contained therein and can send modified instances back to the
middleware for storing in the database.
Typically, a visual component is bound to an attribute that directly belongs to the entity in the
datasource. In the example above, the component is bound to the customer attribute of the Order
entity.
A component can also be associated with an attribute of a related entity, for example,
customer.name. In this case, the component will display the value of the name attribute, however
when the user changes the value, the datasource listeners will not be invoked and the changes will not
be saved. Therefore, it makes sense to bind the component to second-level entity attributes only if
they are intended for display. For example in a Label, a Table column, or in a TextField, where
editable = false.
Datasource is a simple datasource designed to work with one entity instance. The instance is set by
the setItem() method and is accessed via getItem().
DatasourceImpl class is the standard implementation of such datasource, which is used, for instance,
as a main datasource on entity edit screens.
CollectionDatasource is a datasource designed to work with a collection of entity instances. The
collection is loaded with the invocation of the refresh() method, instance keys are accessible through
the getItemIds() method. The setItem() method sets the "current" instance of the collection and
getItem() returns it (for example, the one that corresponds to the currently selected table row).
The way of loading collections is determined by implementation. The most typical one is loading from
Middleware via DataManager; in this case, setQuery(), setQueryFilter() are used to form a
JPQL query.
CollectionDatasourceImpl class is the standard implementation of such datasources, which is
used on screens with entity lists.
GroupDatasource is a subtype of CollectionDatasource, designed to work with the
GroupTable component.
Standard implementation is the GroupDatasourceImpl class.
HierarchicalDatasource is a subtype of CollectionDatasource, designed to work with the
Tree and TreeTable components.
Standard implementation is the HierarchicalDatasourceImpl class.
NestedDatasource is a datasource designed to work with instances that are loaded in an attribute of
another entity. In this case, a datasource that contains a parent entity is accessible via getMaster(),
and meta property that corresponds to the parent attribute containing instances of this datasource is
accessible via getProperty().
For example an Order instance which contains a reference to the Customer instance is set in the
dsOrder datasource. Then, to link the Customer instance with visual components, it is enough to
create NestedDatasource with dsOrder as parent and meta property to point to the
Order.customer attribute.
PropertyDatasource is a subtype of NestedDatasource, designed to work with one instance or
collection of related entities that are not embedded.
Standard implementations: for working with one instance – PropertyDatasourceImpl, with a
collection – CollectionPropertyDatasourceImpl, GroupPropertyDatasourceImpl,
HierarchicalPropertyDatasourceImpl. The latter also implements the
CollectionDatasource interface, however some of its irrelevant methods like setQuery() throw
UnsupportedOperationException.
EmbeddedDatasource is a subtype of NestedDatasource, which contains an instance of an
embedded entity.
Standard implementation is the EmbeddedDatasourceImpl class.
RuntimePropsDatasource is a specific datasource, designed to work with dynamic attributes of
entities.
view="_local">
<query>
select c from sample$Color c order by c.name
</query>
</collectionDatasource>
</dsContext>
In the example above, carDs contains one entity instance, Car, and nested allocationsDs and
repairsDs contain collections of related entities from the Car.driverAllocations and Car.repairs
attributes, respectively. The Car instance together with related entities is set into the datasource from the
outside. If this screen is an edit screen, it happens automatically when opening the screen. The colorsDs
datasource contains a collection of instances of the Color entity, which is loaded by the datasource itself
using the specified JPQL query with the _local view.
dsContext elements:
softDeletion – the false value disables the soft deletion mode when loading entities, i.e., deleted
instances will also be loaded. Default value is true.
collectionDatasource elements:
query – query to load entities
groupDatasource – completely similar to collectionDatasource, but creates datasource
implementation that is suitable to use in conjunction with the GroupTable component.
hierarchicalDatasource – similar to collectionDatasource, and creates datasource
implementation that is suitable to use in conjunction with the Tree and TreeTable components.
hierarchyProperty is a specific attribute. It specifies an attribute name, upon which a hierarchy is
built.
A datasource implementation class is selected implicitly based on the name of the XML element and, as
mentioned above, the mutual arrangement of elements. However, if you need to apply a custom datasource,
you can explicitly specify its class in the datasourceClass attribute.
The DsBuilder instance is parameterized by an invocation chain of its methods in the fluent interface
style. If the master and property parameters are set, then NestedDatasource will be created,
otherwise – Datasource or CollectionDatasource.
Example:
CollectionDatasource ds = new DsBuilder(getDsContext())
.setJavaClass(Order.class)
.setViewName(View.LOCAL)
.setId("ordersDs")
.buildCollectionDatasource();
For example:
public class MyDatasource extends CustomCollectionDatasource<SomeEntity, UUID> {
@Override
protected Collection<SomeEntity> getEntities(Map<String, Object> params) {
return someService.getEntities();
}
}
To create a custom datasource instance declaratively, specify the custom class name in the
datasourceClass attribute of the datasource XML element. In case of programmatic creation via
DsBuilder, specify the class by invoking setDsClass() or as a parameter of one of the build*()
methods.
For example, a query of the datasource of the Customer entity may look as follows:
select c from sales$Customer c
or
select o.customer from sales$Order o
The ds prefix
The parameter value is data from another datasource that is registered in the same DsContext. For
example:
<collectionDatasource id="customersDs" class="com.sample.sales.entity.Customer"
view="_local">
<query>
select c from sales$Customer c
</query>
</collectionDatasource>
</collectionDatasource>
In the example above, a query parameter of the ordersDs datasource will be a current entity instance
located in the customersDs datasource.
If parameters with the ds prefix are used, dependencies between datasources are created automatically.
They lead to updating the datasource if its parameter are changed. In the example above, if the selected
Customer is changed, the list of its Orders is changed automatically.
Please note that in the example of the parameterized query, the left part of the comparison operator is
the value of the o.customer.id identifier, and the right part – the Customer instance that is contained
in the customersDs datasource. This comparison is valid since when running a query at Middleware,
the implementation of the Query interface, by assigning values to query parameters, automatically adds
entity ID instead of a passed entity instance.
A path through the entity graph to an attribute (from which the value should be used) can be specified in
the parameter name after the prefix and name of a datasource, for example:
<query>
select o from sales$Order o where o.customer.id = :ds$customersDs.id
</query>
or
<query>
select o from sales$Order o where o.tagName = :ds$customersDs.group.tagName
</query>
ordersDs.refresh(ParamsMap.of("number", "1"));
Bringing an instance to its identifier, if necessary, is performed similarly to parameters with the ds prefix.
The path through the entity graph in the parameter name is not supported in this case.
Bringing an instance to its identifier, if necessary, is similar to ds parameters. In this case, the path
through the entity graph in the parameter name is not supported.
The easiest way to provide such ability is to connect a special visual component, Filter, to a datasource.
If by any reason the use of a universal filter is unwanted, a special XML markup can be embedded into a
query text. This will allow to create a resulting query based on values entered by the user into any visual
components of the screen.
filter – a root element of the filter. It can directly contain only one condition.
and, or – logical conditions, may contain any number of other conditions and statements.
c – JPQL statement, which is added into the where section. It contains only the text and an optional
join attribute, which value will be added into a corresponding place of the query.
Conditions and statements are added into the resulting query only if parameters inside contain values, i.e.,
they are not null.
Use only custom, param and component parameters in query filters. ds and session parameters will
not work as expected.
Example:
<query>
select distinct d from app$GeneralDoc d
<filter>
<or>
<and>
<c join=", app$DocRole dr">dr.doc.id = d.id and d.processState =
:custom$state</c>
<c>d.barCode like :component$barCodeFilterField</c>
</and>
<c join=", app$DocRole dr">dr.doc.id = d.id and dr.user.id =
:custom$initiator</c>
</or>
</filter>
</query>
In this case, if state and initiator parameters are passed into the refresh() method of a
datasource, and a visual component, barCodeFilterField, has some value specified, then the resulting
query will be as follows:
select distinct d from app$GeneralDoc d, app$DocRole dr
where
(
(dr.doc.id = d.id and d.processState = :custom$state)
and
(d.barCode like :component$barCodeFilterField)
)
or
(dr.doc.id = d.id and dr.user.id = :custom$initiator)
If, for example, the barCodeFilterField component is empty and only one parameter, initiator, was
passed into the refresh() method, the query will be as follows:
select distinct d from app$GeneralDoc d, app$DocRole dr
where
(dr.doc.id = d.id and dr.user.id = :custom$initiator)
The (?i) prefix should be specified before a parameter name and not inside the value.
The parameter value will be automatically converted to lowercase.
If the parameter value does not have % characters, they will be added to the beginning and the end.
In this case, the parameter value taken from the customerNameField component will be converted to
lowercase and will be framed with % characters, and then an SQL query with a lower(C.NAME) like ?
Please note that with this search, an index created in the DB by the NAME field, will not be used.
@Inject
private CollectionDatasource<Employee, UUID> employeesDs;
@Override
public void init(Map<String, Object> params) {
employeesDs.addItemPropertyChangeListener(event -> {
log.info("Property {} of {} has been changed from {} to {}",
event.getProperty(), event.getItem(), event.getPrevValue(),
event.getValue());
});
employeesDs.addStateChangeListener(event -> {
log.info("State of {} has been changed from {} to {}",
event.getDs(), event.getPrevState(), event.getState());
});
employeesDs.addItemChangeListener(event -> {
log.info("Datasource {} item has been changed from {} to {}",
event.getDs(), event.getPrevItem(), event.getItem());
});
employeesDs.addCollectionChangeListener(event -> {
log.info("Datasource {} content has been changed due to {}",
event.getDs(), event.getOperation());
});
}
}
attribute of an entity contained in the datasource is changed. The modified entity instance itself, the
name of changed attribute, old and new values can be obtained from the event object passed to the
listener.
The ItemPropertyChangeListener can be used to react to changes made in an entity instance by
UI components, i.e. when a user edits input fields.
ItemChangeListener is added by the Datasource.addItemChangeListener() method. The
listener is invoked when a selected entity instance returned by the Datasource.getItem() method is
changed.
For Datasource, it happens when another instance (or null) is set to the datasource with setItem()
method.
For CollectionDatasource, this listener is invoked when a selected element is changed in a linked
visual component. For example, it may be a selected table row, tree element or item in a drop-down list.
StateChangeListener is added by the Datasource.addStateChangeListener() method. The
listener is invoked when a state of the datasource is changed. The datasource can be in one of three
states corresponding to the Datasource.State enumeration:
NOT_INITIALIZED – datasource has just been created.
INVALID – the whole DsContext, which this datasource is related to, is created.
VALID – datasource is ready: Datasource contains an entity instance or null,
CollectionDatasource – collection of instances or an empty collection.
Receiving a notification about changes in datasource state may be important for complex editors, which
consist of several frames where it is difficult to trace the moment of setting an edited entity into the
datasource. In this case, StateChangeListener can be used for the delayed initialization of certain
screen elements:
employeesDs.addStateChangeListener(event -> {
if (event.getState() == Datasource.State.VALID)
initDataTypeColumn();
});
4.5.3.4. DsContext
All datasources that are created declaratively are registered in the DsContext object which belongs to a
screen. A reference to DsContext can be obtained using the getDsContext() method of a screen
controller or via Controller Dependency Injection.
1. Organizes dependencies between datasources when navigation through a record set in one datasource
(i.e. changing a "current" instance with the setItem() method) causes a related datasource to be
updated. These dependencies allow you to organize master-detail relationships between visual
components on screens.
Dependencies between datasources are organized using query parameters with the ds$ prefix.
2. Collects all changed entity instances and sends them to Middleware in a single invocation of
DataManager.commit(), i.e. to save them into the database in a single transaction.
As an example, let’s assume that some screen allows a user to edit an instance of the Order entity and
a collection of OrderLine instances belonging to it. The Order instance is located in Datasource;
the OrderLine collection – in nested CollectionDatasource, which is created using the
Order.lines attribute. If user changes some attribute of Order and creates a new instance,
OrderLine. Then, when a screen is committed to DataManager, two instances – changed Order and
new OrderLine – will be sent simultaneously. After that, they will together be merged into one
persistent context and saved into the database on the transaction commit. The OrderLine instance is
also contained in the Order.lines collection, but if it’s not passed into persistent context
independently, the cascade merging between Order and OrderLines at the ORM level should be set.
Tight cascade relations at the ORM level sometimes cause unwanted consequences in unexpected
places, so it is better to avoid them, as described in the DsContext mechanism.
As a result of committing the transaction, DsContext receives a set of saved instances from
Middleware (in the case of optimistic locking they, at least, have an increased value of the version
attribute), and sets these instances in datasources replacing old ones. It allows you to work with the
latest instances immediately after committing without an extra datasource refresh that produces queries
to Middleware and the database.
3. Declares two listeners: BeforeCommitListener and AfterCommitListener. They receive
notifications before and after committing modified instances. BeforeCommitListener enables to
supplement a collection of entities sent to DataManager to save arbitrary entities in the same
transaction. A collection of saved instances that are returned from DataManager can be obtained after
commit in the AfterCommitListener listener.
This mechanism is required if some entities, with which a screen works, are not under control of
datasources, but are created and changed directly in the controller code. For example, a visual
component, FileUploadField, after uploading a file, creates a new entity instance, FileDescriptor,
which can be saved together with other screen entities by adding to CommitContext in
BeforeCommitListener.
In the following example, a new instance of Customer will be sent to Middleware and saved to the
database together with other modified screen entities when the screen is committed:
protected Customer customer;
@Override
public void init(Map<String, Object> params) {
getDsContext().addBeforeCommitListener(context -> {
if (customer != null)
context.getCommitInstances().add(customer);
}
}
4.5.3.5. DataSupplier
DataSupplier – interface, through which the datasources refer to Middleware for loading and saving
entities. The standard implementation simply delegates to DataManager. A screen can define its
implementation of the DataSupplier in dataSupplier attribute of the window element. Such own
implementation may, for example, call an additional middleware block for loading data for the screen from
different database.
A reference to DataSupplier can be obtained either by injection into a screen controller or through the
DsContext or Datasource instances. In both cases, an own implementation is returned if defined for the
screen.
actionPerform() is invoked by a visual component associated with this action. An instance of the
caller is passed to the method.
getId() returns an identifier of the action. The identifier is usually set by a constructor of a class that
implements Action and does not change throughout the lifecycle of the created action object.
Methods for getting and setting caption, description, shortcut, icon, enabled, visible
properties. Typically, all these properties are used by related visual components to set their own
corresponding properties.
addPropertyChangeListener(), removePropertyChangeListener() methods used to add and
remove listeners which handle changes to the abovementioned properties. A listener receives
notification of java.beans.PropertyChangeEvent type, which contains the name of the changed
property, its old and new values.
refreshState() - a method that can be implemented in a particular action class to initialize the
abovementioned properties in accordance to some external factors, such as user rights. It is usually
invoked in constructors of implementing classes or from related visual components.
addOwner(), removeOwner(), getOwner(), getOwners() – methods used to control relation
between the action and visual components.
It is recommended to implement actions using the declarative creation or by inheriting from the BaseAction
class. Furthermore, there is a set of standard actions applicable for tables and picker components. You can
also derive action classes from standard actions to modify their behavior or to intercept events.
Visual component with a single action implements the Component.ActionOwner interface. These are
Button and LinkButton.
Action is linked to the component by the invocation of the ActionOwner.setAction() component
method. At this point, the component replaces its properties with corresponding properties of the action
(see components overview for details).
Visual component containing several actions implements the Component.ActionsHolder interface.
These are Window, Frame, Table and its inheritors, Tree, PopupButton, PickerField, LookupPickerField.
The ActionsHolder.addAction() method is used to add actions to the component. Implementation
of this method in the component checks whether it already contains an action with the same identifier. If
yes, then the existing action will be replaced with the new one. Therefore, it is possible, for example, to
declare a standard action in a screen descriptor and then create a new one in the controller with
overridden methods and add it to the component.
<actions>
<action id="sayHelloAction" caption="msg://sayHello" shortcut="ALT-T"
invoke="sayHello"/>
</actions>
<layout>
<button action="sayHelloAction"/>
</layout>
</window>
// controller
In the example above, an action with sayHelloAction identifier and a name from message pack is
declared. This action is bound with a button, which caption will be set to the action name. The action will
invoke the sayHello() controller method when clicking on the button, or when pressing the ALT-T
shortcut if at that moment the screen has input focus.
Declaring actions for PopupButton:
<popupButton caption="Say something">
<actions>
<action id="helloAction" caption="Say hello" invoke="sayHello"/>
<action id="goodbyeAction" caption="Say goodbye" invoke="sayGoodbye"/>
</actions>
</popupButton>
In this example copy and changePassw actions are declared in addition to create and edit standard
actions of the table. These actions invoke corresponding methods of the controller. In addition, the
trackSelection="true" attribute is specified for them, which means that the action and
corresponding button become disabled if no row is selected in the table. It is useful if the action is
intended to be executed over a currently selected table row.
An optional openType attribute can be specified for create and edit actions to define edit screen
opening mode, as described for the setOpenType() method of the CreateAction class.
Declaring PickerField actions:
<pickerField id="colourField" datasource="carDs" property="colour">
<actions>
<action id="lookup"/>
<action id="show" icon="icons/show.png"
invoke="showColour" caption="" description="Show colour"/>
</actions>
</pickerField>
In the example above, the standard lookup action and an additional show action invoking the
showColour() method of the controller, are declared for the PickerField component. Since
PickerField buttons that display actions use icons instead of captions, the caption attribute is
explicitly set to an empty string, otherwise action name and button caption would be set to the action
identifier. The description attribute allows you to display a tooltip when hovering over the action
button.
You can obtain references to any declared actions in the screen controller either directly by injection, or from
components that implement the Component.ActionsHolder interface. This can be useful to set action
properties programmatically. For example:
@Named("carsTable.create")
private CreateAction createAction;
@Named("carsTable.copy")
private Action copyAction;
@Inject
private PickerField colourField;
@Override
public void init(Map<String, Object> params) {
Map<String, Object> values = new HashMap<>();
values.put("type", CarType.PASSENGER);
createAction.setInitialValues(values);
copyAction.setEnabled(false);
identifiers; therefore, for the declaration of a standard action in XML, it is enough to specify its identifier.
CreateAction
CreateAction – action with create identifier. It is intended to create new entity instance and open its edit
screen. If the edit screen successfully commits a new instance to the database, CreateAction adds this
new instance to the table data source and makes it selected.
setOpenType() allows you to specify new entity edit screen open mode. THIS_TAB by default.
Since it is quite often required to open edit screens in another mode (typically, DIALOG), you can specify
an openType attribute with desired value in the action element when using declarative creation of the
create action. This eliminates the need to obtain action reference in the controller and set this property
programmatically. For example:
<table id="usersTable">
<actions>
setWindowId() allows you to specify the identifier of the entity edit screen. By default,
{entity_name}.edit is used, for example sales$Customer.edit.
setWindowParams() allows you to set edit screen parameters passed into its init() method.
setInitialValues() allows you to set initial values of attributes of the entity being created. It takes a
Map object, where keys are attribute names, and values are attribute values. For example:
Map<String, Object> values = new HashMap<>();
values.put("type", CarType.PASSENGER);
carCreateAction.setInitialValues(values);
@Override
public void init(Map<String, Object> params) {
customersTableCreate.setAfterCommitHandler(new CreateAction.AfterCommitHandler() {
@Override
public void handle(Entity entity) {
showNotification("Committed", NotificationType.HUMANIZED);
}
});
}
afterWindowClosed() is the last method invoked by the action after closing the edit screen
regardless of whether the new entity has been committed or not. This method does not have
implementation and can be overridden in inheritors to handle this event.
setAfterWindowClosedHandler() allows you to provide a handler which will be called after closing
the edit screen regardless of whether the new entity has been committed or not. This handler can be
used instead of overriding afterWindowClosed() to avoid creating the action subclass.
EditAction
EditAction is an action with edit identifier, intended to open an edit screen for a selected entity instance.
If the edit screen successfully commits the instance to the database, then EditAction updates this
instance in the table datasource.
setOpenType() allows you to specify entity edit screen open mode. THIS_TAB by default.
Since it is quite often required to open edit screens in another mode (typically DIALOG), you can specify
openType attribute with desired value in the action element when creating the action declaratively.
This eliminates the need to obtain action reference in the controller and set this property
programmatically. For example:
<table id="usersTable">
<actions>
<action id="edit" openType="DIALOG"/>
setWindowId() allows you to specify entity edit screen identifier. {entity_name}.edit is used by
default, for example, sales$Customer.edit.
setWindowParams() allows you to set edit screen parameters, passed to its init() method.
afterCommit() is invoked by the action after the entity has been successfully committed and the edit
screen has been closed. This method does not have implementation and can be overridden in inheritors
to handle this event.
setAfterCommitHandler() allows you to provide a handler which will be called after the new entity
has been successfully committed and the edit screen has been closed. This handler can be used instead
of overriding afterCommit() to avoid creating the action subclass. For example:
@Named("customersTable.edit")
private EditAction customersTableEdit;
@Override
public void init(Map<String, Object> params) {
customersTableEdit.setAfterCommitHandler(new EditAction.AfterCommitHandler() {
@Override
public void handle(Entity entity) {
showNotification("Committed", NotificationType.HUMANIZED);
}
});
}
afterWindowClosed() is the last method invoked by the action after closing the edit screen
regardless of whether the edited entity has been committed or not. This method does not have
implementation and can be overridden in inheritors to handle this event.
setAfterWindowClosedHandler() allows you to provide a handler which will be called after closing
the edit screen regardless of whether the new entity has been committed or not. This handler can be
used instead of overriding afterWindowClosed() to avoid creating the action subclass.
RemoveAction
RemoveAction - action with remove identifier, intended to remove a selected entity instance.
setAutocommit() allows you to control the moment of entity removal from the database. By default
commit() method is invoked after triggering the action and removing the entity from the datasource. As
result, the entity is removed from the database. You can set autocommit property into false using
setAutocommit() method or corresponding parameter of the constructor. In this case you will need to
explicitly invoke the datasource commit() method to confirm the removal after removing the entity from
the datasource.
@Override
public void init(Map<String, Object> params) {
customersTableRemove.setAfterRemoveHandler(new RemoveAction.AfterRemoveHandler() {
@Override
public void handle(Set removedItems) {
showNotification("Removed", NotificationType.HUMANIZED);
}
});
}
RefreshAction
RefreshAction - an action with refresh identifier. It is intended to update (reload) entities collection.
When triggered, it invokes refresh() method of a datasource associated with the corresponding
component.
AddAction
AddAction – action with add identifier, intended for selecting an existing entity instance and adding it to the
collection. When triggered, opens entities lookup screen.
setOpenType() allows you to specify entity selection screen open mode. THIS_TAB by default.
Since it is often required to open the lookup screens in a different mode (usually DIALOG), the
openType attribute can be specified in the action element, when creating the add action declaratively.
This eliminates the need to get a reference to the action in the controller and set this property
programmatically. For example:
<table id="usersTable">
<actions>
<action id="add" openType="DIALOG"/>
ExcludeAction
ExcludeAction - an action with exclude identifier. It allows a user to exclude entity instances from a
collection without removing them from the database. The class of this action is an inheritor of
RemoveAction, however, when triggered it invokes excludeItem() of CollectionDatasource
instead of removeItem(). In addition, for an entity in a nested datasource, the ExcludeAction
disconnects the link with the parent entity. Therefore this action can be used for editing one-to-many
associations.
The following specific methods are defined in the ExcludeAction class in addition to RemoveAction:
setConfirm() – flag to show the removal confirmation dialog. You can also set this property via the
action constructor. By default it is set to false.
ExcelAction
ExcelAction - an action with excel identifier, intended to export table data into XLS and download the
resulting file. You can add this action only to Table, GroupTable and TreeTable components.
When creating the action programmatically, you can set the following constructor parameters:
LookupAction
LookupAction – action with lookup identifier, intended for selecting an entity instance and setting it as the
setLookupScreenOpenType() allows you to specify entity selection screen open mode. THIS_TAB
by default.
setLookupScreen() allows you to specify entity selection screen identifier.
{entity_name}.lookup by default, for example, sales$Customer.lookup. If such screen does
not exist, attempts to open {entity_name}.browse screen, for example,
sales$Customer.browse.
setLookupScreenParams() allows you to set selection screen parameters, passed into its init()
method.
afterSelect() is invoked by the action after the selected instance is set as the component’s value.
This method does not have implementation and can be overridden.
afterCloseLookup() is the last method invoked by the action after closing the lookup screen
regardless of whether an instance has been selected or not. This method does not have implementation
and can be overridden.
ClearAction
ClearAction - an action with clear identifier, intended for clearing (i.e. for setting to`null`) the value of the
component.
OpenAction
OpenAction - action with open identifier, intended for opening an edit screen for the entity instance which
is the current value of the component.
setEditScreenOpenType() allows you to specify entity selection screen open mode. THIS_TAB by
default.
setEditScreen() allows you to specify entity edit screen identifier. {entity_name}.edit screen is
used by default, for example, sales$Customer.edit.
setEditScreenParams() allows you to set edit screen parameters, passed to its `init()`method.
afterWindowClosed() is invoked by the action after closing the edit screen. This method does not
have implementation and can be overridden in inheritors to handle this event.
4.5.4.3. BaseAction
BaseAction is a base class for actions implementation. It is recommended to derive custom actions from it
when declarative actions creation functionality is insufficient.
When creating a custom action class, you should implement actionPerform() method and pass action
identifier to the BaseAction constructor. You can override any property getters: getCaption(),
getDescription(), getIcon(), getShortcut(), isEnabled(), isVisible(). Standard
implementations of these methods return values set by setter methods, except the getCaption() method.
If the action name is not explicitly set by setCaption() method, it retrieves message using action
identifier as key from the the localized message pack corresponding to the action class package. If there is
no message with such key, then the key itself, i.e. the action identifier, is returned.
BaseAction can change its enabled and visible properties depending on user permissions and current
context.
Usage examples:
Button action:
@Inject
private Button helloBtn;
@Override
public void init(Map<String, Object>params) {
helloBtn.setAction(new BaseAction("hello") {
@Override
public void actionPerform(Component component) {
showNotification("Hello!", NotificationType.TRAY);
}
});
}
In this example, the helloBtn button caption will be set to the string located in the message pack with
the hello key. You can override the getCaption() action method to initialize button name in a
different way.
Action of a programmatically created PickerField:
@Inject
private ComponentsFactory componentsFactory;
@Inject
private BoxLayout box;
@Override
public void init(Map<String, Object>params) {
PickerField pickerField = componentsFactory.createComponent(PickerField.NAME);
pickerField.addAction(new BaseAction("hello") {
@Override
public String getCaption() {
return null;
}
@Override
public String getDescription() {
return getMessage("helloDescription");
}
@Override
public String getIcon() {
return"icons/hello.png";
}
@Override
public void actionPerform(Component component) {
showNotification("Hello!", NotificationType.TRAY);
}
});
box.add(pickerField);
}
In this example an anonymous BaseAction derived class is used to set the action of the picker field
button. The button caption is not displayed, as an icon with a description, which pops up when hovering
mouse cursor, is used instead.
Table action:
@Inject
private Table table;
@Inject
private Security security;
@Override
public void init(Map<String, Object> params) {
table.addAction(new HelloAction());
}
public HelloAction() {
super("hello");
}
@Override
public void actionPerform(Component component) {
showNotification("Hello " + table.getSingleSelected(), NotificationType.TRAY);
}
@Override
protected boolean isPermitted() {
return security.isSpecificPermitted("myapp.allow-greeting");
}
@Override
public boolean isApplicable() {
return target != null && target.getSelected().size() == 1;
}
}
In this example, the HelloAction class is declared, and its instance is added to the table’s actions list.
The action is enabled for users who have myapp.allow-greeting security permission and only when
a single table row is selected. The latter is possible because BaseAction’s target property is
automatically assigned to the action when it is added to a ListComponent descendant (Table or
Tree).
If you need an action, which becomes enabled when one or more table rows are selected, use
BaseAction’s descendant - ItemTrackingAction, which adds default implementation of
isApplicable() method:
@Inject
private Table table;
@Override
public void init(Map<String, Object> params) {
table.addAction(new ItemTrackingAction("hello") {
@Override
public void actionPerform(Component component) {
showNotification("Hello " + table.getSelected().iterator().next(),
NotificationType.TRAY);
}
});
}
Dialogs have a title with a closing button and are always displayed in the center of the application main
window. Notifications can be displayed both in the center and in the corner of the window, and can
automatically disappear.
4.5.5.1. Dialogs
Dialogs are invoked by showMessageDialog() and showOptionDialog() methods of the Frame
interface. This interface is implemented by screen controller, so these methods can be invoked directly in
the controller code.
showMessageDialog() is intended to display a message. The method has the following parameters:
title – dialog title.
message - message. For HTML type (see below), you can use HTML tags for formatting the
message. When using HTML, make sure you escape data loaded from the database to avoid code
injection in web client. You can use \n characters for line breaks in non-HTML messages.
showOptionDialog() is intended to display a message and buttons for user actions. In addition to
parameters described for showMessageDialog(), the method takes an array or a list of actions. A
button is created for each dialog action. After a button is clicked, the dialog closes invoking
actionPerform() method of the corresponding action.
It is convenient to use anonymous classes derived from DialogAction for buttons with standard
names and icons. Five types of actions defined by the DialogAction.Type enum are supported: OK,
CANCEL, YES, NO, CLOSE. Names of corresponding buttons are extracted from the main message pack.
Below is an example of a dialog invocation with Yes and No buttons and with a caption and messages
taken from the message pack of the current screen:
showOptionDialog(
getMessage("confirmCopy.title"),
getMessage("confirmCopy.msg"),
MessageType.CONFIRMATION,
new Action[] {
new DialogAction(DialogAction.Type.YES, Status.PRIMARY) {
public void actionPerform(Component component) {
copySettings();
}
},
new DialogAction(DialogAction.Type.NO, Status.NORMAL)
}
);
The Status parameter of DialogAction is used to assign a special visual style for a button
representing the action. Status.PRIMARY highlights the corresponding button and makes it selected.
The Status parameter can be omitted, in this case default highlighting is applied.
4.5.5.2. Notifications
Notifications can be invoked using showNotification() method of the Frame interface. This interface is
implemented by screen controlller, so this method can be invoked directly from the controller code.
caption - notification text. In case of HTML-type (see below), you can format message text using
HTML-tags. When using HTML, don’t forget to escape data to prevent code injection in the web-client.
You can use \n characters for line breaks in non-HTML messages.
description – an optional description displayed under the caption. You can also use \n character or
HTML-formatting.
1. Define a task as an inheritor of the BackgroundTask abstract class. Pass a link to a screen controller
which will be associated with the task and the task timeout to the task constructor.
Closing the screen will interrupt the tasks associated with it. Additionally, the task will be interrupted
automatically after the specified timeout.
Actual actions performed by the task are implemented in the run() method.
2. Create an object of BackgroundTaskHandler class controlling the task by passing the task instance
to the handle() method of the BackgroundWorker bean. A link to BackgroundWorker can be
obtained by an injection in a screen controller, or through the AppBeans class.
3. Run the task by invoking the execute() method of BackgroundTaskHandler.
Example:
@Inject
protected BackgroundWorker backgroundWorker;
@Override
public void init(Map<String, Object> params) {
// Create task with 10 sec timeout and this screen as owner
BackgroundTask<Integer, Void> task = new BackgroundTask<Integer, Void>(10, this) {
@Override
public Void run(TaskLifeCycle<Integer> taskLifeCycle) throws Exception {
// Do something in background thread
for (int i = 0; i < 5; i++) {
TimeUnit.SECONDS.sleep(1); // time consuming computations
taskLifeCycle.publish(i); // publish current progress to show it in
progress() method
}
return null;
@Override
public void canceled() {
// Do something in UI thread if the task is canceled
}
@Override
public void done(Void result) {
// Do something in UI thread when the task is done
}
@Override
public void progress(List<Integer> changes) {
// Show current progress in UI thread
}
};
// Get task handler object and run the task
BackgroundTaskHandler taskHandler = backgroundWorker.handle(task);
taskHandler.execute();
}
If you need to use certain values of visual components in the task thread, you should implement their
acquisition in getParams() method, which runs in the UI thread once, when a task starts. In the run()
method, these parameters will be accessible via the getParams() method of the TaskLifeCycle
object.
If any exception occurs, the framework invokes BackgroundTask.handleException() method in
the UI thread, which can be used to display the error.
Background tasks are affected by cuba.backgroundWorker.maxActiveTasksCount and
cuba.backgroundWorker.timeoutCheckInterval application properties.
In Web Client, background tasks are implemented using HTTP push provided by the Vaadin
framework. See https://vaadin.com/wiki/-/wiki/Main/Working+around+push+issues for information on
how to set up your web servers for this technology.
If you don’t use background tasks, but want to update UI state from a non-UI thread, use methods of
the UIAccessor interface. You should get a reference to UIAccessor using the
BackgroundWorker.getUIAccessor() method in the UI thread, and after that you can invoke its
access() and accessSynchronously() methods from a background thread to safely read or
modify the state of UI components.
4.5.7. Themes
Themes are used to manage the visual presentation of the application.
Themes are defined in SCSS files. To modify (extend) a theme in the project, you should create a specific
file structure in the web module. A convenient way to do this is to use CUBA Studio: open the Project
properties section and click Create theme extension. Select the theme you want to extend in the popup
window. As a result, the following directory structure will be created in the modules/web directory (for Halo
theme extension):
themes/
halo/
branding/
app-icon-login.png
app-icon-menu.png
favicon.ico
halo-ext-defaults.scss
halo-ext.scss
styles.scss
Apart from that, the build.gradle script will be complemented with the buildScssThemes task, which is
executed automatically each time the web module is built. The optional deployThemes task can be used to
quickly apply changes in themes to the running application.
Changing branding
You can configure some branding properties, such as icons, login and main application window captions,
and the website icon (favicon.ico).
To set window captions and the login window welcome text, edit Project properties in CUBA Studio and
click Branding at the bottom of the page. Set window captions and the login window welcome text using
the appropriate links.
These parameters are saved in the main message pack of the gui module (i.e the modules/gui
/<root_package>/gui/messages.properties file and its variants for different locales). Message
packs allow you to use different image files for different user locales. The sample
messages.properties file:
application.caption = MyApp
application.logoImage = branding/myapp-menu.png
The path to favicon.ico is not specified since it must be located in the root directory of the theme.
Adding icons
Image files that will be used in the icon properties for actions and visual components, e.g. Button, can be
added to your theme extension.
For example, to add an icon to the Halo theme extension, you have to add the image file to the
modules/web/themes/halo directory described above (it is recommended to create a subfolder):
themes/
halo/
images/
address-book.png
After that, you can use the icon in the application by specifying the path relatively to the theme directory in
the icon property:
<action id="adresses"
icon="images/address-book.png"/>
Font elements of Font Awesome can be used instead of icons. You should specify the name of the
required constant of the com.vaadin.server.FontAwesome class in the icon property with a
font-icon: prefix, for example:
<action id="adresses"
icon="font-icon:BOOK"/>
In the web module create the enum class implementing com.vaadin.server.FontIcon interface for
the new icons:
import com.vaadin.server.FontIcon;
import com.vaadin.server.GenericFontIcon;
HEADPHONES(0XE900),
SPINNER(0XE905);
IcoMoon(int codepoint) {
this.codepoint = codepoint;
}
@Override
public String getFontFamily() {
return FONT_FAMILY;
}
@Override
public int getCodepoint() {
return codepoint;
}
@Override
public String getHtml() {
return GenericFontIcon.getHtml(FONT_FAMILY, codepoint);
}
@Override
public String getMIMEType() {
throw new UnsupportedOperationException(FontIcon.class.getSimpleName()
+ " should not be used where a MIME type is needed.");
}
Add new styles to the theme extension. We recommend creating a special subfolder fonts in the main
folder of theme extension, for example, modules/web/themes/halo/com.company.demo/fonts.
Put the styles and font files in their own subfolders, for example, fonts/icomoon. Files of fonts are
represented by the following extensions: .eot, .svg, .ttf, .woff. The set of fonts icomoon from an
open library, used in this example, consists of 4 joint used files: icomoon.eot, icomoon.svg,
icomoon.ttf, icomoon.woff.
Create a file with styles that includes @font-face and a CSS class with the icon style. Below is an
example of the icomoon.scss file, where IcoMoon class name corresponds to the value returned by
FontIcon#getFontFamily method:
@mixin icomoon-style {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
@font-face {
font-family: 'icomoon';
src:url('icomoon.eot?hwgbks');
src:url('icomoon.eot?hwgbks#iefix') format('embedded-opentype'),
url('icomoon.ttf?hwgbks') format('truetype'),
url('icomoon.woff?hwgbks') format('woff'),
url('icomoon.svg?hwgbks#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
.IcoMoon {
@Include icomoon-style;
}
Create a reference to the file with font styles in halo-ext.scss or other file of theme extension:
@import "fonts/icomoon/icomoon";
Create new class App.java extending DefaultApp in the root package of your web module and
register it in web-spring.xml as the cuba_App bean to override the bean for central class of the
application infrastructure, for example:
<bean name="cuba_App" class="com.company.sample.web.App" scope="prototype"/>
Now you can use new icons by direct reference to their class and enum element in XML-descriptor of the
screen:
<button caption="Headphones" icon="ico-moon-icon:HEADPHONES"/>
Consider the example of setting the yellow background color to the field displaying the customer’s name.
The field elements of FieldGroup do not have the stylename attribute, therefore we have to set the
field’s style name in the controller:
@Named("fieldGroup.name")
private TextField nameField;
@Override
public void init(Map<String, Object> params) {
nameField.setStyleName("name-field");
}
In the halo-ext.scss file, add the new style definition to the halo-ext mixin:
@import "../halo/halo";
@mixin halo-ext {
@include halo;
.name-field {
background-color: lightyellow;
}
}
@mixin halo-ext {
@include halo;
.v-menubar-menuitem-caption {
font-weight: bold;
}
}
Below is the example of a Halo theme extension, since it is based on Valo theme from Vaadin, and
provides the widest range of options for customization.
$v-support-inverse-menu: true;
The sample halo-ext-defaults.scss for a theme with a dark background and slightly minimized
margins is provided below:
$v-background-color: #444D50;
$v-font-size--h1: 22px;
$v-font-size--h2: 18px;
$v-font-size--h3: 16px;
$v-layout-margin-top: 8px;
$v-layout-margin-left: 8px;
$v-layout-margin-right: 8px;
$v-layout-margin-bottom: 8px;
$v-layout-spacing-vertical: 8px;
$v-layout-spacing-horizontal: 8px;
$v-table-row-height: 25px;
$v-table-header-font-size: 13px;
$v-table-cell-padding-horizontal: 5px;
$v-support-inverse-menu: false;
1. In CUBA Studio, open Project properties section and click Create theme extension. Select halo
and click Create. A Halo theme extension will be created in the project as described in the previous
section.
2. Rename the themes/halo directory in the web module to themes/facebook, then rename the
halo-ext.scss file inside it to facebook.scss, and halo-ext-defaults.scss to facebook-
defaults.scss:
themes/
facebook/
branding/
facebook.scss
facebook-defaults.scss
favicon.ico
styles.scss
.facebook {
@include facebook;
}
.v-theme-version {
display: none;
}
@mixin facebook {
@include halo;
}
$v-border-radius: 0;
$v-textfield-border-radius: 0;
$v-link-text-decoration: none;
$v-shadow: 0 1px 0 (v-shade 0.2);
$v-bevel: inset 0 1px 0 v-tint;
$v-unit-size: 30px;
$v-gradient: v-linear 12%;
$v-overlay-shadow: 0 3px 8px v-shade, 0 0 0 1px (v-shade 0.7);
$v-shadow-opacity: 20%;
$v-selection-overlay-padding-horizontal: 0;
$v-selection-overlay-padding-vertical: 6px;
$v-selection-item-border-radius: 0;
$v-line-height: 1.35;
$v-font-size: 14px;
$v-font-weight: 400;
$v-unit-size: 25px;
$v-font-size--h1: 22px;
$v-font-size--h2: 18px;
$v-font-size--h3: 16px;
$v-layout-margin-top: 8px;
$v-layout-margin-left: 8px;
$v-layout-margin-right: 8px;
$v-layout-margin-bottom: 8px;
$v-layout-spacing-vertical: 8px;
$v-layout-spacing-horizontal: 8px;
$v-table-row-height: 25px;
$v-table-header-font-size: 13px;
$v-table-cell-padding-horizontal: 5px;
6. Create the facebook-theme.properties file in the src directory of the web module with the
following content:
@include=halo-theme.properties
You can use this file to override server-side theme variables from the halo-theme.properties file
of the platform.
7. Add the following properties to the web-app.properties file:
cuba.web.theme = facebook
cuba.themeConfig = havana-theme.properties halo-theme.properties facebook-
theme.properties
8. Rebuild the application and start the server. Now the user will see the application in Facebook theme
on first login, and will be able to choose between Facebook, Halo and Havana in the Help > Settings
menu.
In Halo theme, Font Awesome icons are used for standard actions and platform screens by default (if
cuba.web.useFontIcons is enabled). In this case, you can replace a standard icon by setting the required
mapping between the icon and the font element name in <your_theme>-theme.properties file. For
example, to use "plus" icon for the create action in the new Facebook theme, the web/src
/facebook-theme.properties file should contain the following:
@include=halo-theme.properties
cuba.web.icons.create.png = PLUS
The fragment of the standard users browser screen in the Facebook theme with the modified create
action:
To add any changes to the standard theme, you need to create a res.nimbus package in the
com.sample.sales.desktop package of the desktop module. Theme files will be stored in the
res.nimbus package.
The icons folder contains icon files, the nimbus.xml file contains the description of the theme style.
com/sample/sales/desktop/res
Adding an icon
If you need to add a new icon to a desktop application, for example an icon for a button, you should
create a res.nimbus.icons package within the com.sample.sales.desktop package of the
desktop module and put the corresponding icon there.
Description of a button in the descriptor with a path to an icon set in the icon attribute:
<button id="button1" caption="Attention" icon="icons/attention.png"/>
The nimbus.xml file with the following content should be created in the res.nimbus package:
<theme xmlns="http://schemas.haulmont.com/cuba/desktop-theme.xsd">
<ui-defaults>
<color property="cubaRequiredBackground" value="#f78260"/>
</ui-defaults>
</theme>
The ui-defaults element redefines the values of platform theme properties set by default.
The ui-defaults element includes both the properties contained in a standard Nimbus
(http://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/_nimbusDefaults.html) theme and the
properties created in the CUBA platform.
In this example, we redefined the value of the CUBA property – cubaRequiredBackground, which
stores the background color for required fields. This change will affect all required input fields.
To create a style like that you need to define style element in the theme file nimbus.xml in the
following way:
<theme xmlns="http://schemas.haulmont.com/cuba/desktop-theme.xsd">
<style name="boldlabel">
<font style="bold"/>
</style>
</theme>
style element can also contain other elements which can define different properties: background,
foreground, icon.
You should add stylename attribute with the name of the created style into the description of the
corresponding label in an xml-descriptor.
<label id="label1" value="msg://labelVal" stylename="boldlabel"/>
In such way the style will be applied only to the labels that have stylename attribute with the value of
boldlabel.
Let us create a custom style that will be applied to the Button component. With this style, the the button
caption will be underlined.
attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
jButton.setFont(originalFont.deriveFont(attributes));
}
}
The component attribute of the style element contains the name of the component that the style with
the name button-underlined can be applied to.
The custom element should contain a path to the decorator class defined above.
When creating an XML element for a button that should have the custom style, specify the style name in
the stylename attribute:
<button stylename="button-underlined" caption="decorated"/>
App - the central class of the application infrastructure. Contains links to Connection and other
infrastructure objects. Only one instance of App exists for a given HTTP session. You can get a
reference to the App instance by using the App.getInstance() static method. If you want to
customize functionality of App in your project, create a class extending DefaultApp in the root package
of your web module and register it in web-spring.xml as the cuba_App bean, for example:
<bean name="cuba_App" class="com.company.sample.web.MyApp" scope="prototype"/>
implementing the TopLevelWindow marker interface. In Studio, go to the Screens section and click
Create login window. If you override the init() method, make sure you invoke
super.init(params).
AppMainWindow – main application window displayed after a user logs in. You can customize the main
window by extending AppMainWindow or create completely new one by extending
AbstractMainWindow and defining desired layout in XML descriptor. In Studio, go to the Screens
section and click Create main window. If you override the init() method, make sure you invoke
super.init(params).
You can control certain main window parameters without redefinig the default implementation using the
following application properties:
cuba.web.foldersPaneEnabled - enables creation of folders pane.
cuba.web.appWindowMode – sets default mode for the main window: tabbed or single screen
(TABBED or SINGLE). Users can change the mode later using Help > Settings screen.
cuba.web.maxTabCount – when the main window is in the tabbed mode, this property sets the
maximum number of tabs that a user can open. The default value is 7.
WindowManager - the central class implementing application screens management logic.
openWindow(), openEditor(), showMessageDialog() and other methods of the Frame interface
implemented by screen controllers delegate to the window manager. WindowManager class is located
in the platform’s common gui module and is abstract. The web module has a dedicated
WebWindowManager class that implements web client specifics. You can get a reference to
WindowManager from any Window implementation (e.g. a screen controller), or via the
WindowManagerProvider bean.
The methods accept a class of the underlying component to be returned, for example:
com.vaadin.ui.TextField vTextField = textField.unwrap(com.vaadin.ui.TextField.class);
You can also use the unwrap() and getComposition() static methods of the WebComponentsHelper
class, passing a CUBA component into them.
Please note that if a screen is located in the project’s gui module, you can only work with generalized
interfaces of CUBA components. In order to use unwrap() you should either put the entire screen into the
web module, or use the controller companions mechanism.
The main window is defined by a specific screen with mainWindow identifier. Its controller should be
derived from the AbstractMainWindow class.
The following special components may be used in the main window in addition to the standard UI
components:
In order to define the special components, add the xmlns:main namespace to the screen:
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
xmlns:main="http://schemas.haulmont.com/cuba/mainwindow.xsd"
class="com.company.sample.gui.MainWindow">
<layout>
</layout>
</window>
The initial screen layout (initialLayout) is removed from AppWorkArea when the first application
screen is opened, and added back when all screens are closed. You can add
AppWorkArea.StateChangeListener to handle changing the work area between the initial layout and
application screens. Such listener can, for example, refresh the initial layout data.
The standard main window implementation may be fully replaced with a custom one. For example:
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
xmlns:main="http://schemas.haulmont.com/cuba/mainwindow.xsd"
class="com.company.sample.gui.MainWindow">
<layout expand="middlePanel">
<hbox margin="true"
stylename="gray"
width="100%">
<label align="MIDDLE_CENTER"
value="Header"/>
</hbox>
<main:menu width="100%"/>
<split id="middlePanel"
orientation="horizontal"
pos="80"
width="100%">
<main:workArea id="workArea"
height="100%"
width="100%">
<main:initialLayout stylename="red">
<label align="MIDDLE_CENTER"
value="Work Area (Initial Layout)"/>
</main:initialLayout>
</main:workArea>
<main:foldersPane height="100%"
stylename="blue"
width="100%"/>
</split>
<hbox margin="true"
stylename="gray"
width="100%">
<label align="MIDDLE_CENTER"
value="Footer"/>
</hbox>
</layout>
</window>
The cuba.web.showBreadCrumbs application property allows you to hide the navigation panel
App – central class of the desktop application infrastructure. Contains links to Connection and main
TopLevelFrame, as well as methods for initialization and retrieval of application settings.
In your application, you should create a custom class – inheritor of App and override the following
methods:
getDefaultAppPropertiesConfig() - should return a string where all application properties files
should be listed separated by spaces:
@Override
protected String getDefaultAppPropertiesConfig() {
return "/cuba-desktop-app.properties /desktop-app.properties";
}
getDefaultHomeDir() - should return path to the folder, where temporary and work files should be
stored. For example:
@Override
protected String getDefaultHomeDir() {
return System.getProperty("user.home") + "/.mycompany/sales";
}
getDefaultLogConfig() - should return name of the Logback configuration file, if it is defined for
the project. For example:
@Override
protected String getDefaultLogConfig() {
return "sales-logback.xml";
}
Additionally, for your custom class inheriting from the App you should define main() method in the
following way:
public static void main(final String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
app = new App();
app.init(args);
app.show();
app.showLoginDialog();
}
});
}
Connection - is a class that provides the functionality of connecting to middleware and storing a user
session.
LoginDialog – the dialog to enter credentials. In your application you can create an inheritor of
LoginDialog and redefine the createLoginDialog() method of the App class to use it.
TopLevelFrame – inheritor of JFrame, which is the top level window. The application has at least one
instance of this class created when application is started and containing the main menu. This instance is
returned by the getMainFrame() method of the App class.
When a user detaches tabs from the main window or a TabSheet (see detachable attribute), additional
instances of TopLevelFrame that do not contain main menu are created.
WindowManager - the central class implementing application screens management logic.
openEditor(), showMessageDialog() and other methods of the Frame interface implemented by
screen controllers delegate to the window manager. WindowManager class is located in the platform’s
common gui module and is abstract. The desktop desktop module has a dedicated
DesktopWindowManager class that implements desktop client specifics.
Typically, WindowManager is not used in the application code directly.
ExceptionHandlers - contains a collection of client-level exception handlers.
The methods accept a class of the underlying component to be returned, for example:
javax.swing.JButton jButton = button.unwrap(javax.swing.JButton.class);
You can also use the unwrap() and getComposition() static methods of the
DesktopComponentsHelper class, passing a CUBA component into them.
Please note that if a screen is located in the project’s gui module, you can only work with generalized
interfaces of CUBA components. In order to use unwrap() you should either put the entire screen in the
desktop module, or use the controller companions mechanism.
There are three levels of integration of a new component into the platform.
On the first level, the new component becomes available as a native Vaadin component. An application
developer can use this component in screen controllers directly: create a new instance and add it to an
unwrapped container. All methods of creating new components described above give you a component
on this level of integration.
On the second level, the new component is integrated into CUBA Generic UI. In this case, from an
application developer perspective, it looks the same as a standard component from the visual
components library. The developer can define the component in a screen XML descriptor or create it
through ComponentsFactory in a controller. See an example in the Integrating a Vaadin Component
into the Generic UI section.
On the third level, the new component is available on the Studio components palette and can be used in
the WYSIWYG layout editor. See an example in the Support for Custom Visual Components in CUBA
Studio section.
provide an alternative web-interface, which is usually intended for users outside of the organization;
provide an interface for integration with mobile applications and third-party systems.
A specific application may contain several portal modules intended for different purposes; for example, in an
application, which automates business tasks, it can be a public web site for customers, an integration
module for a mobile application for ordering a taxi, an integration module for a mobile application for drivers,
etc.
The cuba application component includes the portal module, which is a template to create portals in
projects. It provides basic functionality of the client block to work with Middleware. Besides, the universal
REST API, included to the portal module as a dependency, is turned on by default.
Below is an overview of the main components provided by the platform in the portal module.
PortalLogoutHandler – handles the navigation to the logout page. It must be registered in the
portal-security-spring.xml project file.
We recommend using the REST API Version 2 for new projects. Version 1 exists for backward
compatibilty only.
The universal REST API of the platform enables loading and saving any entities defined in the application
data model by sending simple HTTP requests. This provides easy way to integrate with a wide range of
third-party applications – from the JavaScript code executed in the browser to arbitrary systems running on
Java, .NET, PHP or any other platform.
Loading entity instances from the database by identifier or by JPQL query with parameters.
Saving new and modified instances, deleting instances.
Obtaining a description of the data model in HTML format.
Data representation in JSON and XML formats.
Middleware service calls.
User authentication.
For the web module the REST API functions will be available by default at the {host:port}/app
/dispatch/api URL.
To create the portal module in your application project, open the project in CUBA Studio and click the
Create portal module link on the Project properties navigator panel. After that, the REST API functions
will be available at the {host:port}/app-portal/api URL.
REST API controllers are registered in the Spring context defined by the dispatcher-spring.xml file of of the
corresponding module (portal-dispatcher-spring.xml or web-dispatcher-spring.xml):
<context:component-scan base-package="com.haulmont.cuba.restapi"/>
All functions require an authenticated user session, which means that you must perform the login first and
then pass the resulting session identifier to subsequent requests.
4.7.1.2.1. Login
Login can be performed by either GET or POST request.
GET request
For GET request, create the URL {host:port}/app-portal/api/login with the following
parameters:
u − user login
p − user password
l − user locale (optional)
For example:
http://localhost:8080/app-portal/api/login?u=admin&p=admin&l=ru
POST request
To perform login using POST, execute request by {host:port}/app-portal/api/login address,
passing JSON (Content-Type header has the value application/json) or form (Content-Type
header has the value application/x-www-form-urlencoded) in request body.
The service will return userSessionId in response body and status 200 or status 401 if the authentication
fails.
To login through REST API, the user must have cuba.restApi.enabled specific permission. Notice that
the user will have the permission if there are no roles explicitly revoking it.
4.7.1.2.2. Logout
Logout can also be performed by either GET or POST request.
GET request
To perform login using GET, construct the URL {host:port}/app-portal/api/logout with the
session parameter containing the current session ID obtained by calling login.
For example:
http://localhost:8080/app-portal/api/logout?session=64f7d59d-2cf5-acfb-f4d3-f55b7882da72
POST request
To perform login using POST, send request to {host:port}/app-portal/api/logout URL, passing
JSON (Content-Type header has the value application/json) or form (Content-Type header
has the value application/x-www-form-urlencoded) in the request body.
format element of the request specifies the result format. It takes two values: xml and json.
max (optional) − maximum number of rows in resulting dataset (similar to JPA setMaxResults).
first (optional) − number of the first row of resulting dataset (similar to JPA setFirstResult).
format specifies the format of obtaining the result. It takes two values: xml or json.
Examples:
http://localhost:8080/app-portal/api/query.json?e=sales$Customer&
q=select+c+from+sales$Customer+c&s=748e5d3f-1eaf-4b38-bf9d-8d838587367d&view=_local
http://localhost:8080/app-portal/api/query.json?e=sales$Customer&
q=select+c+from+sales$Customer+c+where+c.name=:name&s=748e5d3f-1eaf-4b38-bf9d-8d838587367d&
name=Smith
For each of the passed parameters, the type can be explicitly specified by adding the parameter of the same
name and the _type suffix to the request. For example:
http://localhost:8080/app-portal/api/query.json?e=sales$Customer&
q=select+c+from+sales$Customer+c+where+c.name=:name&s=748e5d3f-1eaf-4b38-bf9d-8d838587367d&
name=Smith&name_type=string
Specifying parameter type is optional, however it allows you to avoid parsing errors if the system cannot
determine the type automatically. Normally, the type should be specified only for string parameters, which
for some reason have a more specific format types (dates, numbers, UUID), but must be interpreted as
strings. The list of available types can be found in meta-model description (Help > Data Model) or by
obtaining the HTML-description of the model.
Example POST request of JSON format, the Content-Type header must be set to application/json:
http://localhost:8080/app-portal/api/query.json?s=748e5d3f-1eaf-4b38-bf9d-8d838587367d
Example POST request of XML format, the Content-Type header must be set to text/xml:
http://localhost:8080/app-portal/api/query.xml?s=748e5d3f-1eaf-4b38-bf9d-8d838587367d
JSON format
application/json should be used as the value of the Content-Type header.
Creating the Order entity, specifying a link to a new Customer entity and filling the Customer entity with
attributes:
{
"commitInstances": [
{
"id": "NEW-sales$Order",
"amount": 15,
"customer": {
"id": "NEW-sales$Customer-b32e43e8-d4d9-11e2-8c8b-2b2939d67fff"
}
},
{
"id": "sales$Customer-b32e43e8-d4d9-11e2-8c8b-2b2939d67fff",
"name": "Fletcher",
"email": "fletcher@mail.com"
}
]
}
{
"id": "sales$Customer-b32a6412-d4d9-11e2-a20b-87b22b1460c7-customer-edit",
"name": "John Doe",
"channel": null
}
]
}
Here, the customer-edit view must contain the channel attribute, otherwise the value will not
change.
The removeInstances array contains removed entities. When removing an entity, you must specify
the value of the id field. Before deletion, merge() will be executed for the provided object, which
enables checking if the version of the removed object has changed.
The softDeletion field controls soft deletion mode.
XML format
text/xml should be used as the value of Content-Type header.
</CommitRequest>
In case of an XML request, fields are set to null with the help of the null="true" attribute. In addition to
that, the identifier must contain the view, which contains the attribute. For example:
<CommitRequest>
<commitInstances>
<instance id="Order-9873c8a8-d4e7-11e2-85c0-33423bc08c84">
<field name="amount" null="true"/>
<reference name="customer" null="true"/>
</instance>
</commitInstances>
</CommitRequest>
The schema containing the description of the function call result is located at
http://schemas.haulmont.com/cuba/6.3/restapi-instance-v2.xsd
For example:
http://localhost:8080/app-portal/api/download?s=abbfb51c-715d-ced5-cc00-ee355278ea21&
f=dbea7543-7761-3680-9b6c-c06f7fdb3738
Service method call can be performed both by GET and POST requests. Additionally, POST requests allow
passing entities or entity collections to the invoked method.
Request format:
{host:port}/app-portal/api/service.<format>?service=<serviceName>&method=<methodName>&
view=<view>¶m0=<value 0>¶mN=<value N>¶m0_type=<type 0>¶mN_type=<type
N>&s=<sessionId>
format - defines the output format. Two values are accepted: xml or json.
service - the name of the service called.
method - the name of the method invoked.
param0 .. paramN - parameter values of the method.
param0_type .. paramN_type - parameter types of the method.
s - the current session identifier.
If a service has a single method with the specified name and number of parameters, explicit parameter type
definition is not required. In other cases, parameter type must be specified.
Request format:
{host:port}/app-portal/api/service?s=<sessionId>
JSON or XML with the description of the method call is passed in the request body.
JSON format
The Content-Type header value is application/json.
{
"service": "refapp_PortalTestService",
"method": "updateCarVin",
"view": "carEdit",
"params": {
"param0": {
"id": "ref$Car-32261b09-b7f7-4b8c-88cc-6dee6fa8e6ab",
"vin": "WV00001",
"colour" : {
"id": "ref$Colour-b32a6412-d4d9-11e2-a20b-87b22b1460c7",
"name": "Red"
},
"driverAllocations": [
{
"id": "ref$DriverAllocation-b32e43e8-d4d9-11e2-8c8b-2b2939d67fff"
},
{
"id": "NEW-ref$DriverAllocation"
}
]
},
"param1": "WV00001",
"param0_type": "com.haulmont.refapp.core.entity.Car",
"param1_type": "java.lang.String"
}
}
XML format
The Content-Type header value is text/xml.
<ServiceRequest xmlns="http://schemas.haulmont.com/cuba/restapi-service-v2.xsd">
<service>refapp_PortalTestService</service>
<method>updateCarVin</method>
<view>carEdit</view>
<params>
<param name="param0">
<instance id="ref$Car-32261b09-b7f7-4b8c-88cc-6dee6fa8e6ab">
<field name="vin">WV00000</field>
<reference name="colour">
<instance id="ref$Colour-b32a6412-d4d9-11e2-a20b-87b22b1460c7">
<field name="name">Red</field>
</instance>
</reference>
<collection name="driverAllocations">
<instance id="ref$DriverAllocation-b32e43e8-d4d9-11e2-8c8b-
2b2939d67fff"/>
<instance id="NEW-ref$DriverAllocation"/>
</collection>
</instance>
</param>
<param name="param1">WV00001</param>
<param name="param0_type">com.haulmont.refapp.core.entity.Car</param>
<param name="param1_type">java.lang.String</param>
</params>
</ServiceRequest>
If a service has a single method with the specified name and number of parameters, explicit parameter
type definition is not required. In other cases, parameter types must be specified.
<param> element may contain both plain text (for setting primitive type values) and nested <instance>
elements for entities or <instances> for entity collections.
primitive Java types. long, int, boolean, etc. should be specified as the type name.
primitive Java type wrappers. The full class name should be specified as the type name:
java.lang.Boolean, java.lang.Integer, etc.
string (java.lang.String).
date (java.util.Date).
UUID (java.util.UUID).
BigDecimal (java.math.BigDecimal)
entity (for POST requests only). The full class name should be specified as the type name, e.g.
com.haulmont.cuba.security.entity.User.
entity collections (for POST requests only). The full class or collection interface name should be
specified as the type name, e.g. java.util.List.
The result may be in JSON or XML, depending on the method call declaration. Currently, methods can
return primitive data types, entities and entity collections.
Entity:
{
"result": {
"id" : "ref$Colour-b32e43e8-d4d9-11e2-8c8b-2b2939d67fff",
"name": "Red"
}
}
Entity:
<result>
<instance id="ref$Colour-b32a6412-d4d9-11e2-a20b-87b22b1460c7">
<field name="name">Red</field>
</instance>
</result>
REST API Version 2 uses the OAuth2 protocol for authentication and supports anonymous access.
The detailed documentation for the API is written according to Swagger specification and is available
at the following URL: http://files.cuba-platform.com/swagger.
Any running CUBA application also exports the swagger documentation at the URLs http://HOST:PORT
/APP_NAME/rest/v2/docs/swagger.yaml and http://HOST:PORT/APP_NAME/rest/v2
/docs/swagger.json.
<param name="number2"/>
</method>
<method name="emptyMethod"/>
<method name="overloadedMethod">
<param name="intParam" type="int"/>
</method>
<method name="overloadedMethod">
<param name="stringParam" type="java.lang.String"/>
</method>
</service>
</services>
Method parameter types can be omitted if the service doesn’t contain an overloaded method with the same
number of parameters. Otherwise, types must be defined.
cuba.rest.client.tokenExpirationTimeSec - defines a token expiration time for the default client in seconds.
cuba.rest.maxUploadSize - defines a maximum file size that can be uploaded with the REST API.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.company.test.services.SomeService;
@RestController
@RequestMapping("/myapi")
@Inject
protected SomeService someService;
@GetMapping("/dosmth")
public String doSmth() {
return someService.getResult();
}
}
2. Create a new Spring configuration file with name rest-dispatcher-spring.xml under the root
package (com.company.test) of web or portal module. The content of the file must be as follows:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema
/context/spring-context-4.3.xsd http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-4.1.xsd">
<security:http pattern="/rest/myapi/**"
create-session="stateless"
entry-point-ref="oauthAuthenticationEntryPoint"
xmlns="http://www.springframework.org/schema/security">
<!-- Specify one or more protected URL patterns-->
<intercept-url pattern="/rest/myapi/**" access="isAuthenticated()"/>
<anonymous enabled="false"/>
<csrf disabled="true"/>
<cors configuration-source-ref="cuba_RestCorsSource"/>
<custom-filter ref="resourceFilter" before="PRE_AUTH_FILTER"/>
<custom-filter ref="cuba_AnonymousAuthenticationFilter"
after="PRE_AUTH_FILTER"/>
</security:http>
</beans>
4. The new controller runs in the context of the CubaRestApiServlet. So the URL for controller
methods will start with the /rest, i.e. the doSmth() method will be accesed by the URL:
http://localhost:8080/app-portal/rest/myapi/dosmth.
URL of the custom controller MUST NOT start with the /rest/v2.
Example:
{
"id": "fa430b56-ceb2-150f-6a85-12c691908bd1",
"number": "OR-000001",
"items": [
{
"id": "82e6e6d2-be97-c81c-c58d-5e2760ae095a",
"description": "Item 1"
},
{
"id": "988a8cb5-d61a-e493-c401-f717dd9a2d66",
"description": "Item 2"
}
],
"__securityToken":
"0NXc6bQh+vZuXE4Fsk4mJX4QnhS3lOBfxzUniltchpxPfi1rZ5htEmekfV60sbEuWUykbDoY+rCxdhzORaYQNQ=="
}
As mentioned earlier, a detailed information about REST API methods is written according to Swagger
specification and is available at address http://files.cuba-platform.com/swagger.
An access to this endpoint is protected with a basic authentication. REST API client identifier and password
is used for basic authentication. Please note that these are not an application user login and password.
REST API client id and password are defined in the application properties cuba.rest.client.id and
cuba.rest.client.secret (the default values are client and secret). You must pass the client id and secret,
separated by a single colon (":") character, within a base64 encoded string in the Authorization header.
grant_type=password&username=smith&password=qwerty123
A base URL for getting all instances of the sales$Order entity is as follows:
http://localhost:8080/app/rest/v2/entities/sales$Order
To implement all the conditions described above the following request parameters must be specified:
view - a view, that will be used for loading entities. In our case the order-edit-view contains a
customer reference.
limit - a number of instances to be returned.
offset - a position of the first extracted record.
sort - an entity attribute name that will be used for sorting.
An OAuth token must be placed to the Authorization header with the Bearer type:
Authorization: Bearer 29bc6b45-83cd-4050-8c7a-2a8a60adf251
"_entityName": "sales$Order",
"_instanceName": "00001",
"id": "46322d73-2374-1d65-a5f2-160461da22bf",
"date": "2016-10-31",
"description": "Vacation order",
"number": "00001",
"items": [
{
"_entityName": "sales$OrderItem",
"_instanceName": "Beach umbrella",
"id": "95a04f46-af7a-a307-de4e-f2d73cfc74f7",
"price": 23,
"name": "Beach umbrella"
},
{
"_entityName": "sales$OrderItem",
"_instanceName": "Sun lotion",
"id": "a2129675-d158-9e3a-5496-41bf1a315917",
"price": 9.9,
"name": "Sun lotion"
}
],
"customer": {
"_entityName": "sales$Customer",
"_instanceName": "Toby Burns",
"id": "4aa9a9d8-01df-c8df-34c8-c385b566ea05",
"firstName": "Toby",
"lastName": "Burns"
}
},
{
"_entityName": "sales$Order",
"_instanceName": "00002",
"id": "b2ad3059-384c-3e03-b62d-b8c76621b4a8",
"date": "2016-12-31",
"description": "New Year party set",
"number": "00002",
"items": [
{
"_entityName": "sales$OrderItem",
"_instanceName": "Jack Daniels",
"id": "0c566c9d-7078-4567-a85b-c67a44f9d5fe",
"price": 50.7,
"name": "Jack Daniels"
},
{
"_entityName": "sales$OrderItem",
"_instanceName": "Hennessy X.O",
"id": "c01be87b-3f91-7a86-50b5-30f2f0a49127",
"price": 79.9,
"name": "Hennessy X.O"
}
],
"customer": {
"_entityName": "sales$Customer",
"_instanceName": "Morgan Collins",
"id": "5d111245-2ed0-abec-3bee-1a196da92e3e",
"firstName": "Morgan",
"lastName": "Collins"
}
}
]
Please note, that every entity in the response has a _entityName attribute with the entity name and an
_instanceName attribute with the entity instance name.
An OAuth token must be placed to the Authorization header with the Bearer type.
The request body must contain a JSON object that describes a new entity instance, e.g.:
{
"number": "00017",
"date": "2016-09-01",
"description": "Back to school",
"items": [
{
"_entityName": "sales$OrderItem",
"price": 100,
"name": "School bag"
},
{
"_entityName": "sales$OrderItem",
"price": 9.90,
"name": "Pencils"
}
],
"customer": {
"id": "4aa9a9d8-01df-c8df-34c8-c385b566ea05"
}
}
A collection of order items (items) and a customer reference are passed in the request body. Let’s
examine how these attributes will be processed.
import com.haulmont.chile.core.annotations.Composition;
import com.haulmont.chile.core.annotations.NamePattern;
import com.haulmont.cuba.core.entity.StandardEntity;
import com.haulmont.cuba.core.entity.annotation.OnDelete;
import com.haulmont.cuba.core.global.DeletePolicy;
import javax.persistence.*;
import java.util.Date;
import java.util.Set;
@NamePattern("%s|number")
@Table(name = "SALES_ORDER")
@Entity(name = "sales$Order")
public class Order extends StandardEntity {
private static final long serialVersionUID = 7565070704618724997L;
@Column(name = "NUMBER_")
protected String number;
@Temporal(TemporalType.DATE)
@Column(name = "DATE_")
protected Date date;
@Column(name = "DESCRIPTION")
protected String description;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CUSTOMER_ID")
protected Customer customer;
@Composition
@OnDelete(DeletePolicy.CASCADE)
@OneToMany(mappedBy = "order")
protected Set<OrderItem> items;
The items collection property is annotated with the @Composition. REST API methods for entity creation
and update will create a new entity instances for all members of such collections. In our case, two instances
of OrderItem entity will be created with the Order entity.
The customer reference doesn’t have a @Composition annotation, that’s why the REST API will try to
find a client with the given id and set it to the customer field. If the client is not found then an order won’t
be created and the method will return an error.
In case of successful method execution a full object graph of the created entity is returned:
{
"_entityName": "sales$Order",
"id": "5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50",
"date": "2016-09-01",
"description": "Back to school",
"version": 1,
"number": "00017",
"createdBy": "admin",
"createTs": "2016-10-13 18:12:21.047",
"updateTs": "2016-10-13 18:12:21.047",
"items": [
{
"_entityName": "sales$OrderItem",
"id": "3158b8ed-7b7a-568e-aec5-0822c3ebbc24",
"createdBy": "admin",
"price": 9.9,
"name": "Pencils",
"createTs": "2016-10-13 18:12:21.047",
"version": 1,
"updateTs": "2016-10-13 18:12:21.047",
"order": {
"_entityName": "sales$Order",
"id": "5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50"
}
},
{
"_entityName": "sales$OrderItem",
"id": "72774b8b-4fea-6403-7b52-4a6a749215fc",
"createdBy": "admin",
"price": 100,
"name": "School bag",
"createTs": "2016-10-13 18:12:21.047",
"version": 1,
"updateTs": "2016-10-13 18:12:21.047",
"order": {
"_entityName": "sales$Order",
"id": "5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50"
}
}
],
"customer": {
"_entityName": "sales$Customer",
"id": "4aa9a9d8-01df-c8df-34c8-c385b566ea05",
"firstName": "Toby",
"lastName": "Burns",
"createdBy": "admin",
"createTs": "2016-10-13 15:32:01.657",
"version": 1,
"updateTs": "2016-10-13 15:32:01.657"
}
}
An OAuth token must be placed to the Authorization header with the Bearer type.
The request body must contain a JSON object containing only fields we want to update, e.g.:
{
"date": "2017-10-01",
"customer" : {
"id" : "5d111245-2ed0-abec-3bee-1a196da92e3e"
}
}
sales-rest-queries.xml contents:
<?xml version="1.0"?>
<queries xmlns="http://schemas.haulmont.com/cuba/rest-queries.xsd">
<query name="ordersAfterDate" entity="sales$Order" view="order-edit-view">
<jpql><![CDATA[select o from sales$Order o where o.date >= :startDate and o.date
<= :endDate]]></jpql>
<params>
<param name="startDate" type="java.util.Date"/>
<param name="endDate" type="java.util.Date"/>
</params>
</query>
</queries>
An OAuth token must be placed to the Authorization header with the Bearer type.
import com.haulmont.cuba.core.EntityManager;
import com.haulmont.cuba.core.Persistence;
import com.haulmont.cuba.core.Transaction;
import org.springframework.stereotype.Service;
import javax.inject.Inject;
import java.math.BigDecimal;
@Service(OrderService.NAME)
public class OrderServiceBean implements OrderService {
@Inject
private Persistence persistence;
@Override
public BigDecimal calculatePrice(String orderNumber) {
BigDecimal orderPrice = null;
try (Transaction tx = persistence.createTransaction()) {
EntityManager em = persistence.getEntityManager();
orderPrice = (BigDecimal) em.createQuery("select sum(oi.price) from
sales$OrderItem oi where oi.order.number = :orderNumber")
.setParameter("orderNumber", orderNumber)
.getSingleResult();
tx.commit();
}
return orderPrice;
}
}
Before the execution with the REST API a service method invocation must be allowed in the configuration
file. The sales-rest-services.xml file must be created in the main package of the web module (e.g.
com.company.sales). Then the file must be defined in the application properties file of the web module
(web-app.properties).
cuba.rest.servicesConfig = +com/company/sales/sales-rest-services.xml
sales-rest-services.xml contents:
<?xml version="1.0" encoding="UTF-8"?>
<services xmlns="http://schemas.haulmont.com/cuba/rest-services-v2.xsd">
<service name="sales_OrderService">
<method name="calculatePrice">
<param name="orderNumber"/>
</method>
</service>
</services>
To invoke the service method the following GET request must be executed:
http://localhost:8080/app/rest/v2/services/sales_OrderService/calculatePrice?orderNumber=00001
An OAuth token must be placed to the Authorization header with the Bearer type.
A service method may return a result of simple datatype, an entity, an entities collection or a serializable
POJO. In our case a BigDecimal is returned, so the response body contains just a number:
39.2
entities
entities collections
POJOs
Suppose we added a new method to the OrderService created in the previous section:
@Override
public OrderValidationResult validateOrder(Order order, Date validationDate){
OrderValidationResult result=new OrderValidationResult();
result.setSuccess(false);
result.setErrorMessage("Validation of order "+order.getNumber()+" failed.
validationDate parameter is: "+validationDate);
return result;
}
import java.io.Serializable;
The new method has an Order entity in the arguments list and returns a POJO.
Before the invocation with the REST API the method must be allowed, so we add a record to the sales-
rest-services.xml configuration file (it was described in the Service Method Invocation (GET)).
<?xml version="1.0" encoding="UTF-8"?>
<services xmlns="http://schemas.haulmont.com/cuba/rest-services-v2.xsd">
<service name="sales_OrderService">
<method name="calculatePrice">
<param name="orderNumber"/>
</method>
<method name="validateOrder">
<param name="order"/>
<param name="validationDate"/>
</method>
</service>
</services>
The validateOrder service method may be called with the POST request on the address:
http://localhost:8080/app/rest/v2/services/sales_OrderService/validateOrder
In case of the POST request parameters are passed in the request body. The request body must contain a
JSON object, each field of this object corresponds to the service method argument.
{
"order" : {
"number": "00050",
"date" : "2016-01-01"
},
"validationDate": "2016-10-01"
}
An OAuth token must be placed to the Authorization header with the Bearer type.
2016-10-01"
}
As a solution, an OAuth token can also be passed in the request URL as a parameter with the
access_token name.
In this case a URL for downloading the image will look like this:
http://localhost:8080/app/rest/v2/files/44809679-e81c-e5ae-
dd81-f56f223761d6?access_token=a2f0bb4e-773f-6b59-3450-3934cbf0a2d6
For simplicity, we will use modules/web/web/VAADIN folder for storing HTML/CSS/JavaScript files, as the
corresponding folder in the deployed web application is used for serving static resources by default. So you
will not need to make any configuration of your Tomcat application server. The resulting URL will start from
http://localhost:8080/app/VAADIN, so do not use this approach in a real world application - create
a separate web application with its own context instead.
Download jQuery and Bootstrap and copy to modules/web/web/VAADIN folder of your project. Create
customers.html and customers.js files, so the content of the folder should look as follows:
bootstrap.min.css
customers.html
customers.js
jquery-3.1.1.min.js
<div class="form-group">
<label for="loginField">Login:</label>
<input type="text" class="form-control" id="loginField">
</div>
<div class="form-group">
<label for="passwordField">Password:</label>
<input type="password" class="form-control" id="passwordField">
</div>
<button type="submit" class="btn btn-default"
onclick="login()">Submit</button>
</div>
function loadCustomers() {
$.get({
url: 'http://localhost:8080/app/rest/v2/entities/sales$Customer?view=_local',
headers: {
'Authorization': 'Bearer ' + oauthToken,
'Content-Type': 'application/x-www-form-urlencoded'
},
success: function (data) {
$('#customers').show();
Login and password from the user input are sent to the server by the POST request with the Base64-
encoded client credentials in the Authorization header as explained in Getting an OAuth Token section.
If the authentication is successful, the web page receives an access token value from the server, the token
is stored in the oauthToken variable, the loginForm div is hidden and the loggedInStatus div is
shown.
To show the list of customers, the request is sent to the server to get the instances of the sales$Customer
entity, passing the oauthToken value in the Authorization header.
In case the request is processed successfully, the customers div is shown, and the customersList
element is filled with items containing customer names and emails.
TaskScheduler can be used to run methods of arbitrary Spring beans in any application block both at the
middleware and client tiers.
...
<task:scheduled-tasks scheduler="scheduler">
<task:scheduled ref="sales_Processor" method="someMethod" fixed-rate="60000"/>
<task:scheduled ref="sales_Processor" method="someOtherMethod" cron="0 0 1 * *
MON-FRI"/>
</task:scheduled-tasks>
</beans>
In the example above, two tasks are declared, which invoke someMethod() and ` someOtherMethod()` of
sales_Processor bean. someMethod() will be invoked at fixed time intervals (60 seconds) from the
moment of application startup. someOtherMethod() is invoked according to the schedule specified by
Cron expression (for the description of the format of such expressions, see http://quartz-scheduler.org
/documentation/quartz-1.x/tutorials/crontrigger).
The actual launch of tasks is performed by TaskScheduler type bean, which is specified in the
scheduler attribute of the scheduled-tasks element. In this example,
CubaThreadPoolTaskScheduler bean with the scheduler name is used. It is configured in the core
and web modules of the cuba application component (see cuba-spring.xml, cuba-web-spring.xml).
This class contains specific implementation, which performs SecurityContext cleanup in the threads, which
are being launched for execution.
The ability to configure tasks while running an application without restarting the server.
The coordination of singleton tasks in the Middleware cluster, including:
Reliable protection from simultaneous execution.
Binding tasks to servers by priorities.
A singleton task is a task which must be executed only on one server at a certain moment of time. For
example, reading from a queue and sending emails.
Defined by – describes which software object implements the task. Possible values are:
Bean – the task is implemented by a method of a Spring managed bean. Additional attributes:
Bean name – the name of the managed bean.
The bean is listed and available for selection only if it is defined in the core module and has
an interface, which contains methods appropriate for invocation from the task. Services and
beans without an interface are not supported.
Method name – the bean interface method that is executed. The method must either have no
parameters, or all parameters must be of String type. The return type of the method can either
be void or String. In the latter case the returning value will be stored in the executions table
(see Log finish below).
Method parameters – the parameters of the chosen method. Only String type parameters are
supported.
Class – the task is a class that implements the java.util.concurrent.Callable interface. The
class must have a public constructor without parameters. Additional attributes:
Class name – the name of the class.
Script – the task is a Groovy script. The script is executed by Scripting.runGroovyScript(). Additional
attributes:
Script name – the name of the script.
User name – the name of a user on whose behalf the task will be executed. If not specified, the task will
be executed on behalf of the user specified in the cuba.jmxUserLogin application property.
Singleton – indicates that the task is a singleton, i.e. should be run only on one application server.
Server priority works only if Scheduling type is Period and the Start date attribute is not
specified. Otherwise, start occurs at the same time and the interception is impossible.
Log start – flags if the task launch should be registered in the SYS_SCHEDULED_EXECUTION table,
which corresponds to the ScheduledExecution entity.
In the current implementation, if the task is a singleton, the launch is registered regardless of this flag.
Log finish – flags if the task completion should be registered in the SYS_SCHEDULED_EXECUTION
table, which corresponds to the ScheduledExecution entity.
In the current implementation, if the task is a singleton, completion is registered regardless of this flag.
Description – an arbitrary text description of the task.
The task also has activity flag, which can be set in the tasks list screen. Inactive tasks are ignored.
Precise time synchronization of Middleware servers is required for correct execution of singleton
tasks!
Synchronous or asynchronous sending. In case of synchronous sending, the calling code waits till the
message is sent to the SMTP server. In case of asynchronous sending, the message is persisted to the
database and the control is returned immediately to the calling code. The actual sending is done later by
a scheduled task.
Reliable tracking of message sending timestamp or errors in the database for both synchronous and
asynchronous modes.
User interface to search and view information about sent messages, including all message attributes and
content, sending status and the number of attempts.
See an example of using this mechanism in the Sending Emails development recipe.
sendEmail() – synchronous message sending. The calling code is blocked while sending the
message to the SMTP server.
The message can be transmitted in the form of a set of parameters (the comma-separated list of
recipients, subject, content, array of attachments), and in the form of a special EmailInfo object, which
encapsulates all this information and allows you to explicitly set the sender’s address and to form the
message body using a FreeMarker template.
EmailException may be thrown during synchronous sending, containing the information on the
recipient addresses, where delivery has failed, and the corresponding error messages.
During the execution of the method, a SendingMessage instance is created in the database for each
recipient. It has the initial SendingStatus.SENDING status, and SendingStatus.SENT after
successful sending. In case of a message sending error, the message status changes to
SendingStatus.NOTSENT.
sendEmailAsync() – asynchronous message sending. This method returns the list (by the number of
recipients) of SendingMessage instances in SendingStatus.QUEUE status, which were created in
the database. The actual sending is performed with the subsequent call of the
EmailerAPI.processQueuedEmails() method, which should be invoked from a scheduled task
with the desired frequency.
file name (the name field), and, if necessary, the attachment identifier which is unique for this message (the
optional but useful contentId field).
The attachment identifier may be used to insert images in the message body. For this, a unique contentId
(for example, myPic) is specified when creating EmailAttachment. Expression like cid:myPic can be
used as a path to insert the attachment in the message body. So, to insert an image you can specify the
following HTML element:
<img src="cid:myPic"/>
All email sending parameters are available via the EmailerConfig configuration interface.
attempts to send an email. It is used if the attemptsCount parameter is not specified when calling
Emailer.sendEmailAsync().
Default value: 10
cuba.email.maxSendingTimeSec – the maximum expected time in seconds, which is required to
send an email to the SMTP server. It is used for asynchronous sending to optimize the selection of
SendingMessage objects from the DB queue.
Default value: 120
cuba.email.sendAllToAdmin – indicates that all messages should be sent to the
cuba.email.adminAddress address, regardless of the specified recipient’s address. It is recommended to
use this parameter during system development and debugging.
Default value: false
cuba.email.adminAddress – the address, to which all messages are sent if the
cuba.email.sendAllToAdmin property is switched on.
Default value: admin@localhost
cuba.emailerUserLogin – the login of system user, used by asynchronous email sending code to be
able to persist the information to the database. It is recommended to create a separate user (for
example, emailer) without a password, so that it will be impossible to log in under his name via user
interface. This is also convenient to search for messages related to email sending in the server log.
Default value: admin
You can view the current parameter values and send a test message using the
app-core.cuba:type=Emailer JMX bean.
Category - defines a category of objects and the corresponding set of dynamic attributes. The category
must be assigned to some entity type.
For example, there is an entity of the Car type. We can define two categories for it: Truck and
Passenger. The Truck category will contain Load Capacity and Body Type attributes, and the Passenger
category – Number of Seats and Child Seat.
CategoryAttribute - defines a dynamic attribute related to some category. Each attribute describes a
single field of a definite type. The required Code field contains the system name of the attribute. The
Name field contains the human-readable attribute name.
CategoryAttributeValue - dynamic attribute value for a particular entity instance. Dynamic attribute
values are physically stored in the dedicated SYS_ATTR_VALUE table. Each table record has a reference
to some entity (ENTITY_ID column).
An entity instance can have dynamic attributes of all categories related to the entity type. So if you create
two categories of the Car entity mentioned above, you will be able to specify any dynamic attribute from
both categories for a Car instance. If you want to be able to classify an entity instance as belonging to a
single category (a car can be either truck or passenger), the entity must implement Categorized interface. In
this case an entity instance will have the reference to a category, and dynamic attributes from this category
only.
Dynamic attribute values are available through getValue() / setValue() methods for any persistent
entity inherited from BaseGenericIdEntity. An attribute code with the + prefix should be passed to these
dataManager.commit(entity);
In fact, the direct access to attribute values in the application code is rarely needed. Any dynamic attribute
can be automatically displayed in any Table or FieldGroup component bound to a datasource containing the
entity, for which the dynamic attribute was created. The attribute editor described below allows you to
specify screens and components that should show the attribute.
User permissions to access dynamic attributes can be set in the security role editor in the same way as for
regular attributes. Dynamic attributes are displayed with the + prefix.
The category editor allows you to create a new category for an entity and define a set of dynamic attributes.
The category name and the related entity type fields are mandatory. The Default checkbox indicates that
this category will be automatically selected for a new instance of an entity implementing Categorized
interface.
Dynamic attribute editor enables setting the name, system code, value type and the default value of the
attribute.
For all value types, except Boolean, there is also a Width field available to set up the field width in
FieldGroup in pixels or as a percentage. If the Width field is empty, its assumed value is 100%. For the
Enumeration value type, the set of named values is defined in the Enumeration field separated by
comma.
A dynamic attribute also has visibility settings, which define the screens where it should be displayed. By
default, the attribute is invisible on any screen.
In addition to the screen, you can also specify a component in which the attribute is to appear (for example,
for screens, where several FieldGroup components show the fields of the same entity).
If the attribute is marked as visible on a screen, it will automatically appear in all field groups and tables
displaying entities of the corresponding type on the screen.
Access to dynamic attributes can also be restricted by user role settings. Security settings for dynamic
attributes are similar to those for regular attributes.
In order for changes in attribute and visibility settings to take effect, click Apply settings in the categories
browser. Changes can also be applied via Administration > JMX Console by calling the
clearDynamicAttributesCache() method of the app-core.cuba:type=CachingFacade JMX
bean.
The dynamic attribute added to the screen automatically by specifying visibility settings is shown below:
Dynamic attributes can be added to a screen manually. To do this, follow these steps:
In the dsContext section of the screen XML-descriptor, set the loadDynamicAttributes property to
true for a datasource that loads the entity (entities), for example:
<dsContext>
<datasource id="carDs" class="com.company.sample.entity.Car" view="_local"
loadDynamicAttributes="true"/>
</dsContext>
Specify the dynamic attribute code with the + prefix in the property XML attribute of a component
definition:
<textField id="numberOfSeats" datasource="carDs" property="+numberOfSeats"/>
instances. The mainDs attribute must refer to the main datasource, which contains the edited entity.
A regular collectionDatasource to load the list of categories of this entity type.
Example:
<dsContext>
<datasource id="carDs"
class="com.company.sample.entity.Car"
view="carEdit"/>
<runtimePropsDatasource id="runtimePropsDs"
mainDs="carDs"/>
<collectionDatasource id="categories"
class="com.haulmont.cuba.core.entity.Category"
view="_local">
<query>
select c from sys$Category c where c.entityType='sample$Car'
</query>
</collectionDatasource>
</dsContext>
Now, the runtimeProperties visual component may be included in the XML-descriptor of the screen:
<runtimeProperties id="runtimePropsFrame"
runtimeDs="runtimePropsDs"
categoriesDs="categories"/>
Dynamic attributes are represented in JSON and XML documents in the same way as regular attributes,
except for the preceding + symbol.
Pessimistic locking explicitly locks an entity instance when it is opened in the editor. As a result, only one
user can edit this particular entity instance in a given moment of time.
Pessimistic locking mechanism can also be used to manage simultaneous execution of arbitrary processes.
The key benefit is that the locks are distributed, since they are replicated in the Middleware cluster. More
information is available in JavaDocs for the LockManagerAPI and LockService interfaces.
Pessimistic locking can be enabled for any entity class on application development or production stage
using Administration > Locks > Setup screen, or as follows:
Insert a new record with the following field values into the SYS_LOCK_CONFIG table with the following
field values:
ID – an arbitrary UUID-type identifier.
NAME – the name of the object to be locked. For an entity, it should be the name of its meta class.
TIMEOUT_SEC – lock expiration timeout in seconds.
Example:
insert into sys_lock_config (id, create_ts, name, timeout_sec) values (newid(),
current_timestamp, 'sales$Order', 300)
Current state of locks can be tracked via the app-core.cuba:type=LockManager JMX bean or through
the Administration > Locks screen. This screen also enables unlocking of any object.
Programmatic access to entity statistics is available via PersistenceManagerAPI interface on the middle
tier and PersistenceManagerService on the the client tier. Statistics get cached into memory, and as a
result, any direct changes to statistics in the database will only be applied after the server restart or after a
calling to the PersistenceManagerMBean.flushStatisticsCache() method.
The description of the EntityStatistics attributes and their impact on the system behaviour is provided
below:
name (NAME column) – the name of the entity meta-class, for example, sales$Customer.
instanceCount (INSTANCE_COUNT column) – the approximate number of entity instances.
fetchUI (FETCH_UI column) – the size of the data displayed on a page when extracting entity lists.
For example, the Filter component uses this number in the Show N rows field.
maxFetchUI (MAX_FETCH_UI column) – the maximum number of entity instances that can be
extracted and passed to the client tier.
This limit is applied when showing entity lists in such components as LookupField or LookupPickerField,
as well as tables without a filter, when no limitations are applied to the connected datasource via
CollectionDatasource.setMaxResults(). In this case the data source itself limits the number of
extracted instances to maxFetchUI.
lookupScreenThreshold (LOOKUP_SCREEN_THRESHOLD column) – the threshold, measured in
number of entities, which determines when lookup screens should be used instead of dropdowns for
entity searches.
The Filter component takes this parameter into account when choosing filter parameters. Until the
threshold is reached, the system uses the LookupField component, and once the threshold is exceeded,
the PickerField component is used. Hence, if lookup screens should be used for a specific entity in a
filter parameter, it is possible to set the value of lookupScreenThreshold to a value lower than
instanceCount.
PersistenceManagerMBean JMX bean enables setting default values for all of the parameters mentioned
above via DefaultFetchUI, DefaultMaxFetchUI, DefaultLookupScreenThreshold attributes. The
system will use the corresponding default values when an entity has no statistics, which is a common case.
The logs contain information about the time of modification, the user who has modified the entity, and the
new values of the changed attributes. Log entries are stored in the SEC_ENTITY_LOG table corresponding
to the EntityLogItem entity. Changed attribute values are stored in the CHANGES column and are
converted to instances of EntityLogAttr entity when they are loaded by the Middleware.
You can also set up Entity Log by entering some records in the database, if you want to include the
configuration to the database initialization scripts.
LoggedEntity defines the types of entities that should be logged. LoggedEntity has the following
attributes:
name (NAME column) – the name of the entity meta-class, for example, sales$Customer.
auto (AUTO column) – defines if the system should log the changes when EntityLogAPI is called with
auto = true parameter (i.e. called by entity listeners).
manual (MANUAL column) – defines if the system should log the changes when EntityLogAPI is
called with auto = false parameter.
LoggedAttribute defines the entity attribute to be logged and contains a link to the LoggedEntity and
the attribute name.
To set up logging for a certain entity, the corresponding entries should be added into the
SEC_LOGGED_ENTITY and SEC_LOGGED_ATTR tables. For example, logging the changes to name and
grade attributes of the Customer entity can be enabled using:
insert into SEC_LOGGED_ENTITY (ID, CREATE_TS, CREATED_BY, NAME, AUTO, MANUAL)
values ('25eeb644-e609-11e1-9ada-3860770d7eaf', now(), 'admin', 'sales$Customer', true,
true);
The logging mechanism is activated by default. If you want to stop it, set the Enabled attribute of the
app-core.cuba:type=EntityLog JMX bean false and then invoke the its invalidateCache()
operation. Alternatively, set the cuba.entityLog.enabled application property to false and restart the server.
The change log for a certain entity can also be accessed from any application screen by loading a collection
of EntityLogItem and the associated EntityLogAttr instances into the datasources and creating the
visual components connected to these datasources. For example:
<dsContext>
<datasource id="customerDs"
class="com.sample.sales.entity.Customer"
view="customerEdit"/>
<collectionDatasource id="logDs"
class="com.haulmont.cuba.security.entity.EntityLogItem"
view="logView">
<query>
select i from sec$EntityLog i
where i.entityId = :ds$customerDs order by i.eventTs
</query>
<collectionDatasource id="logAttrDs"
property="attributes"/>
</collectionDatasource>
</dsContext>
<layout>
...
<split orientation="vertical" width="100%" height="100%">
</split>
...
</layout>
Logged attributes should contain the @LocalizedValue annotation in order to display localized values. When
annotated, the logging mechanism populates the EntityLogAttr.messagesPack field, and the table in
the example above is able to use locValue column instead of value:
<table id="logAttrTable" width="100%" height="100%">
<columns>
<column id="name"/>
<column id="locValue"/>
</columns>
<rows datasource="logAttrDs"/>
</table>
The whole state (or snapshot) of a graph of entities defined by a specified view is saved.
Snapshot saving mechanism is explicitly called from the application code.
The platform allows the snapshots to be viewed and compared.
The graph of Java objects is converted into XML and saved in the SYS_ENTITY_SNAPSHOT table
(corresponding to the EntitySnapshot enitity) together with the link to the primary entity.
Usually, snapshots need to be saved after editor screen commit. This may be achieved by overriding the
postCommit() method of the screen controller, for example:
public class CustomerEditor extends AbstractEditor<Customer> {
@Inject
protected Datasource<Customer> customerDs;
@Inject
protected EntitySnapshotService entitySnapshotService;
...
@Override
protected boolean postCommit(boolean committed, boolean close) {
if (committed) {
entitySnapshotService.createSnapshot(customerDs.getItem(), customerDs.getView());
}
return super.postCommit(committed, close);
}
}
The snapshots should be loaded into the frame from the edit screen controller:
public class CustomerEditor extends AbstractEditor<Customer> {
@Inject
protected EntityDiffViewer diffFrame;
...
@Override
The diff-view.xml frame shows the list of snapshots for the given entity, with an ability to compare them.
The view for each snapshot includes the user, date and time. When a snapshot is selected from the list, the
changes will be displayed compared to the previous snapshot. All attributes are marked as changed for the
first snapshot. Selecting two snapshots shows the results of the comparison in a table.
The comparison table shows attribute names and their new values. When a row is selected, the detailed
information on attribute changes across two snapshots is shown. Reference fields are displayed according
to their instance name. When comparing collections, the new and removed elements are highlighted with
green and red color respectively. Collection elements with changed attributes are displayed without
highlighting. Changes to element positions are not recorded.
FileDescriptor entity – the descriptor of the uploaded file (not to be confused with
java.io.FileDescriptor) enables referencing the file from the data model objects.
FileStorageAPI interface – provides access to the file storage at the middle tier. Its main methods
are:
saveStream() – saves the contents of the file passed as the InputStream according to the
specified FileDescriptor.
openStream() – returns the contents of the file defined by the FileDescriptor in the form of an
opened InputStream.
FileUploadController class – a Spring MVC controller, which enables sending files from the Client
to the Middleware with HTTP POST requests.
FileDownloadController class – Spring MVC controller which enables retrieving files from the
Middleware to the Client with HTTP GET requests.
FileUpload and FileMultiUpload visual components – enable uploading files from the user’s computer to
the client tier of the application and then transferring them to the Middleware.
FileUploadingAPI interface – temporary storage for files uploaded to the client tier. It is used for
uploading files to the client tier by the visual components mentioned above. The application code can
use putFileIntoStorage() method for moving a file into the persistent storage of the Middleware.
ExportDisplay – client tier interface allowing downloading various application resources to the user’s
computer. Files can be retrieved from persistent storage using the show() method, which requires a
FileDescriptor. An instance of ExportDisplay may be obtained either by calling the
File transfer between the user’s computer and the storage in both directions is always performed by
copying data between the input and output streams. Files are never fully loaded into memory at any
application level, which enables transferring files of almost any size.
The temporary client-level storage (FileUploadingAPI) stores temporary files in the folder defined by
cuba.tempDir application property. Temporary files can remain in the folder in case of any failures. The
clearTempDirectory() method of the cuba_FileUploading bean is invoked periodically by the
scheduler defined in the cuba-web-spring.xml file.
The show() method accepts an optional ExportFormat type parameter, which defines the type of the
content and the file extension. If the format has not been provided, the extension is retrieved from the
FileDescriptor, and the content type is set to application/octet-stream.
The file extension defines whether the file is downloaded via the browser’s standard open/save dialog
(Content-Disposition = attachment), or if the browser will attempt to show the file in the browser
window (Content-Disposition = inline). The list of extensions for files that should be shown in the
browser window is defined by the cuba.web.viewFileExtensions application property.
@Inject
private ExportDisplay exportDisplay;
}
exportDisplay.show(new ByteArrayDataProvider(bytes), "test.html",
ExportFormat.HTML);
}
}
The roots of the structure can be defined in the cuba.fileStorageDir application property in the format of
comma-separated paths list. For example:
cuba.fileStorageDir=/work/sales/filestorage,/mnt/backup/filestorage
If the property is not defined, the storage will be located in the filestorage sub-folder of the Middleware’s
work directory. This folder is tomcat/work/app-core/filestorage in standard Tomcat deployment.
There are three levels of subdirectories representing the files upload date – year, month, and day.
The actual files are saved in the day directory. The file names match the identifiers of the corresponding
FileDescriptor objects. The file extension matches that of the source file.
The root folder of the structure contains a storage.log file with the information on each stored file,
including the user and upload time. This log is not required for operation of the storage mechanism, but it
could be useful for troubleshooting.
The app-core.cuba:type=FileStorage JMX bean displays the current set of storage roots and offers
the following methods for troubleshooting:
The main part of this mechanism is the UniqueNumbers bean with the UniqueNumbersAPI interface. The
bean is available in the Middleware block. The interface has the following methods:
getNextNumber() – get the next value in a sequence. The mechanism enables simultaneous
management of several sequences, identified by arbitrary strings. The name of the sequence from which
you want to retrieve the value is passed in the domain parameter.
Sequences do not require initialization. When getNextNumber() is called for the first time, the
corresponding sequence will be created and 1 will be returned.
getCurrentNumber() – obtain the current, i.e. the last generated value of the sequence. The domain
parameter sets the sequence name.
setCurrentNumber() – set the current value of the sequence. This value incremented by 1 will be
returned by the next call to getNextNumber().
The getNextNumber() method of the UniqueNumbersService service is used to get sequence values
in client blocks.
The app-core.cuba:type=UniqueNumbers JMX bean with methods duplicating the methods of the
UniqueNumbersAPI is used for sequence management.
The implementation of the sequence generation mechanism depends on the DBMS type. Sequence
parameters can also be managed directly in the database, but in different ways.
For HSQL, Microsoft SQL Server 2012+, PostgreSQL and Oracle each UniqueNumbersAPI sequence
corresponds to a SEC_UN_{domain} sequence in the database.
For Microsoft SQL Server before 2012 each sequence corresponds to a SEC_UN_{domain} table with
an IDENTITY field.
For MySQL sequences correspond to records in the SYS_SEQUENCE table.
The platform’s QueryRunner is a variant of Apache DbUtils QueryRunner with the added ability to use Java
Generics.
Usage example:
QueryRunner runner = new QueryRunner(persistence.getDataSource());
try {
Set<String> scripts = runner.query("select SCRIPT_NAME from SYS_DB_CHANGELOG",
new ResultSetHandler<Set<String>>() {
There are two ways of using QueryRunner: current transaction or separate transaction in autocommit
mode.
The following beans must be added into spring.xml file of the core module to use MyBatis in the project:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="cuba-mybatis.xml"/>
<property name="mapperLocations" value="classpath*:com/sample/sales/core/sqlmap/*.xml"/>
</bean>
The MapperLocations parameter defines a path to mapperLocations mapping files (according to the
rules of ResourceLoader Spring interface).
Below is the an example of a mapping file for loading an instance of Order together with a related
Customer and a collection of Order items:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org
/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sample.sales">
</mapper>
The following code can be used to retrieve query results from the example above:
Transaction tx = persistence.createTransaction();
try {
SqlSession sqlSession = AppBeans.get("sqlSession");
At the moment of this writing, the panel is available for the Web Client only.
The platform supports three types of folders: application folders, search folders and record sets. Application
folders are displayed at the top of the panel as a separate folder tree. Search folders and record sets are
displayed at the bottom of the panel in a combined tree.
Application folders:
Open screens with or without a filter.
The set of folders depend on the current user session. Folder visibility is defined by a Groovy script.
Application folders can be created and changed only by users with special permissions.
Folder headers may show the record count calculated by a Groovy script.
Folder headers are updated on timer events, which means that record count and display style for
each folder can be updated.
Search folders:
Open screens with a filter.
Search folders can be local or global, accessible only by the user who created them or by all users,
respectively.
Local folders can be created by any user, while global are created only by users with special
permissions.
Record sets:
Open screens with a filter containing a condition to select specific records by their identifiers.
Record set content can be edited using the dedicated table actions: Add to set and Remove from set.
Record sets are available only to the user who created them.
The following application properties can influence the functionality of the folder panel:
cuba.web.foldersPaneEnabled
cuba.web.foldersPaneVisibleByDefault
cuba.web.foldersPaneDefaultWidth
cuba.web.appFoldersRefreshPeriodSec
cuba.web.showFolderIcons
A simple application folder can be created via the folder panel context menu. Such folder will not be
connected to the system screens and can be only used to group other folders within a folder tree.
Count script – a Groovy script defining the record count and display style for a folder. Executed at
the start of the user session and on timer.
The script should return a numeric value, the integer part of which will be used as the record count
value. If the script is not defined or returns null, the counter will not be displayed. In addition to the
returned value, the script can also set the style variable, which will be used as folder display style.
Example of a Groovy script:
def em = persistence.getEntityManager()
def q = em.createQuery('select count(o) from sales$Order o')
def count = q.getSingleResult()
In order for the style to be displayed, the application theme should contain this style for the
v-tree-node element in cuba-folders-pane, for example:
.cuba-folders-pane .v-tree-node.emphasized {
font-weight: bold;
}
Scripts can use the following variables defined in the groovy.lang.Binding context:
The platform uses the same instance of groovy.lang.Binding for all scripts when the folders are being
updated. So it is possible to pass variables between them in order to eliminate duplicate requests and
increase performance.
Script sources can be stored within the attributes of the AppFolder entity or in separate files. In the latter
case, the attribute should include a file path with a mandatory ".groovy" extension, as required by the
Resources interface. If an attribute contains a string ending with ".groovy", the script will be loaded from the
corresponding file; otherwise, the attribute content itself will be used as a script.
Application folders are instances of the AppFolder entity and are stored in the related SYS_FOLDER and
SYS_APP_FOLDER tables.
Creating global search folders, requires the user to have Create/edit global search folders permission
(cuba.gui.searchFolder.global).
Search folder’s filter can be edited once the folder is created by opening the folder and changing the
Folder:{folder name} filter. Saving the filter will change the folder filter as well.
Search folders are instances of the SearchFolder entity stored in the related SYS_FOLDER and
SEC_SEARCH_FOLDER tables.
<groupTable id="customersTable"
width="100%">
<buttonsPanel>
<button action="customersTable.create"/>
...
</buttonsPanel>
...
Add to set or Add to current set / Remove from set buttons should now appear in table context menu. If
a table includes a buttonsPanel (as in the example above), the corresponding table buttons will also be
added.
Record sets are the instances of the SearchFolder entity stored in the related SYS_FOLDER and
SEC_SEARCH_FOLDER tables.
The Web Client block enables opening application screens via commands embedded into a URL. If the
browser does not have an active application session with the registered user, the application will show the
login screen first, and then, after successful authentication, proceed to the main application window with the
requested screen.
item – an entity instance to be passed to the edit screen, encoded according to conventions of the
EntityLoadInfo class, i.e. entityName-instanceId or entityName-instanceId-viewName.
Examples:
http://localhost:8080/app/open?screen=sec$User.edit&item=sec$User-
60885987-1b61-4247-94c7-dff348347f93
http://localhost:8080/app/open?screen=sec$User.edit&item=sec$User-
60885987-1b61-4247-94c7-dff348347f93-user.edit
params – parameters passed to the screen controller’s init() method. Parameters are encoded as
name1:value1,name2:value2. Parameter values may include entity instances encoded according to
the conventions of the EntityLoadInfo class. Examples:
http://localhost:8080/app/open?screen=sales$Customer.lookup¶ms=p1:v1,p2:v2
http://localhost:8080/app/open?screen=sales$Customer.lookup¶ms=p1:sales$Customer-
01e37691-1a9b-11de-b900-da881aea47a6
LinkHandler bean may be redefined in the application project in order to provide specialized link handling.
The LinkHandler bean is a prototype, so do not forget to specify the scope when defining your bean in
spring.xml, for example:
<!-- web-spring.xml -->
<bean id="cuba_LinkHandler" class="com.company.sample.web.MyLinkHandler"
scope="prototype"/>
This gives the system administrator an opportunity to review and edit the data that is not accessible from
standard screens due to their design, and to create the data model and main menu sections linked to the
entity inspector only, at prototyping stage.
/entity-inspector-browse.xml screen.
If a String-type parameter named entity with an entity name has been passed to the screen, the
inspector will show a list of entities with the abilities for filtering, selection and editing. The parameter can be
specified when registering the screen in screens.xml, for example:
screens.xml
<screen id="sales$Product.lookup"
template="/com/haulmont/cuba/gui/app/core/entityinspector/entity-inspector-
browse.xml">
<param name="entity"
value="sales$Product"/>
</screen>
menu.xml
<item id="sales$Product.lookup"/>
Generally, the screen may be called without any parameters. In this case, the top part will contain an entity
selection field. In the cuba application component, the inspector screen is registered with the
entityInspector.browse identifier, so it can be simply referenced in a menu item:
<item id="entityInspector.browse"/>
Application components of the platform contain their own files with descriptions, like cuba-credits.xml,
reports-credits.xml. The cuba.creditsConfig application property can be used to specify a description
file of the application.
The items element lists the used libraries with license texts included either as an embedded license
element, or as a license attribute with a link to the text in the licenses section.
It is possible to reference licenses declared in the current file as well as any other file declared in
cuba.creditsConfig variable prior to the current one.
The licenses element lists the texts of general licenses used (e.g. LGPL).
The entire list of third-party software components can be displayed using the com/haulmont/cuba/gui
/app/core/credits/credits-frame.xml frame, which loads the information from the files defined in
the cuba.creditsConfig property. An example of the frame within a screen:
<dialogMode width="500" height="400"/>
<layout expand="creditsBox">
<groupBox id="creditsBox"
caption="msg://credits"
width="100%">
<frame id="credits"
src="/com/haulmont/cuba/gui/app/core/credits/credits-frame.xml"
width="100%"
height="100%"/>
</groupBox>
</layout>
If the dialog mode (WindowManager.OpenType.DIALOG) is used when opening the screen that contains
the frame, the height must be specified; otherwise, the scrolling may work not correctly. See the
dialogMode element in the example above.
Below is an example of the first two operations, illustrated by adding the "Address" field to the User entity of
the platform security subsystem.
The new name of the entity should be specified in the @Entity annotation. Since the parent entity does not
declare the inheritance strategy, it is assumed to be SINGLE_TABLE by default. It means that the child entity
will be stored in the same table as the parent one, and the @Table annotation is not required. Other parent
entity annotations ( @NamePattern, @Listeners, etc.) are automatically applied to the child entity, but can
be overridden in its class.
An important element of the new entity class is the @Extends annotation, which takes the parent class as a
parameter. It enables creating a registry of child entities and forces the platform mechanisms to use them
everywhere instead of the parent ones. The registry is implemented by the ExtendedEntities class,
which is a Spring bean named cuba_ExtendedEntities, and is also accessible via the Metadata
interface.
messages.properties
ExtUser.address=Address
messages_ru.properties
ExtUser.address=Адрес
Add the update script for the corresponding table to the database create and update scripts:
-- add column for "address" attribute
alter table SEC_USER add column ADDRESS varchar(100)
^
-- add discriminator column required for entity inheritance
alter table SEC_USER add column DTYPE varchar(100)
^
-- set discriminator value for existing records
update SEC_USER set DTYPE = 'sales$ExtUser' where DTYPE is null
^
In order to use new entity attributes in screens, create views for the new entity with the same names as the
views of the base entity. A new view should extend the base view and define new attributes, for example:
<view class="com.sample.sales.entity.ExtUser"
name="user.browse"
extends="user.browse">
<property name="address"/>
</view>
An extended view is not required if the base one extends _local and you add only local attributes, so in
the described case this step can be omitted.
XML inheritance is implemented by specifying the parent descriptor path in the extends attribute of the root
window element.
If the extending descriptor has a certain element, the corresponding element will be searched for in the
In order to debug the descriptor conversion, you can output the resulting XML to the server log by specifying
the TRACE level for the com.haulmont.cuba.gui.xml.XmlInheritanceProcessor logger in the
Logback configuration file.
In this example, the descriptor is inherited from the standard User entities browser of the platform. The
address column is added to the table with index 2, so it is displayed after login and name.
If you register a new screen in screens.xml with the same identifiers that were used for the parent screen,
the new screen will be invoked everywhere instead of the old one.
<screen id="sec$User.browse"
template="com/sample/sales/gui/extuser/extuser-browse.xml"/>
<screen id="sec$User.lookup"
template="com/sample/sales/gui/extuser/extuser-browse.xml"/>
Once all the abovementioned actions are completed, the application will use ExtUser with the
corresponding screens instead of the standard User entity of the platform.
Screen controller can be extended by creating a new class that is inherited from the base screen controller.
Class name is specified in the class attribute of the root element of the extending XML descriptor; the
usual rules of inheriting XML described above will apply.
To substitute a bean implementation, you should create your own class that implements the interface or
extends the base platform class and register it in spring.xml of the application. You cannot apply the
@Component annotation to the extending class; overriding beans is possible only in the XML configuration.
}
}
Register the class in spring.xml of the project core module with the same identifier as the platform bean:
<bean id="cuba_PersistenceTools" class="com.sample.sales.core.ExtPersistenceTools"/>
After that, the Spring context will always return ExtPersistenceTools instead of the
base`PersistenceTools` instance. A checking code example:
Persistence persistence;
PersistenceTools tools;
persistence = AppBeans.get(Persistence.class);
tools = persistence.getTools();
assertTrue(tools instanceof ExtPersistenceTools);
tools = AppBeans.get(PersistenceTools.class);
assertTrue(tools instanceof ExtPersistenceTools);
tools = AppBeans.get(PersistenceTools.NAME);
assertTrue(tools instanceof ExtPersistenceTools);
5. Application Development
For Java and Groovy code, it is recommended to follow the standard style described in Code
Conventions for the Java Programming Language. When programming in IntelliJ IDEA, you can just use
the default style and Ctrl-Alt-L shortcut for formatting.
The maximum line length is 120 characters. The indentation is 4 characters; using spaces instead of
tabs is enabled.
XML code: indentation is 4 characters; using spaces instead of tabs is enabled.
Naming Conventions
UpperCamelCase
attributesTable
Component identifier, parameter lowerCamelCase, only letters
:component$relevantTo
names in queries and numbers.
:ds$attributesDs
SQL scripts
CUSTOMER
Columns UPPER_CASE
TOTAL_AMOUNT
The project root contains build scripts (build.gradle, settings.gradle) and IntelliJ IDEA project
files.
The modules directory includes the subdirectories of the project modules − global, core, gui, portal, web.
The global module contains the source code directory, src, with configuration files – metadata.xml,
persistence.xml and views.xml. The com.sample.sales.core package contains interfaces of the
Middleware services; the com.sample.sales.entity package contains entity classes and localization
files for them.
The gui module includes the source code directory, src, with the screens.xml configuration file. The
com.sample.sales.gui package contains XML descriptors and screen controllers, and localization files
for them.
src – source code directory with the application properties file of the Web Client block and configuration
files – web-menu.xml, web-permissions.xml, web-screens.xml and web-spring.xml. The
com.samples.sales.web package contains the main class of the Web Client block (inheritor of
DefaultApp) and the main localized messages pack.
web – directory with configuration files of the web application built from the Web Client: context.xml and
web.xml.
This section describes the structure of the scripts and the purpose and parameters of Gradle tasks.
buildscript
The buildscript section of the script defines the following:
A set of repositories for loading project dependencies. When you create a new project in CUBA
Studio, it contains the link to the repository which is selected in the Studio server window. By default it
is the public repository:
https://repo.cuba-platform.com/content/groups/work
If your project uses Premium Add-ons, Studio adds one more repository:
https://repo.cuba-platform.com/content/groups/premium
Both repositories require a user name and a password. While the public repository uses common
credentials which are specified right in the build script (cuba / cuba123), the Premium Add-ons
repository credentials are provided by per-developer subscription. The first part of your license key
before dash is the repository user name, the part after dash is the password. For example, if your key
is 111111222222-abcdefabcdef, then the user name is 111111222222 and the password is
abcdefabcdef.
Studio passes to Gradle credentials for repositories when executes the build script. If you want to build
the project outside Studio, you can pass premiumRepoUser and premiumRepoPass in the
command line arguments with -P prefix:
gradle assemble -PpremiumRepoUser=111111222222 -PpremiumRepoPass=abcdefabcdef
Dependencies used by the build system. The dependecies include the CUBA Gradle plugin and a set
of application components used by the project. Components are specified by their global module
artifact. In the following example, two components are used: com.haulmont.cuba (cuba component
of the platform) and com.company.base (a custom component):
dependencies {
classpath "com.haulmont.gradle:cuba-plugin:$cubaVersion"
classpath "com.haulmont.cuba:cuba-global:$cubaVersion"
classpath "com.company.base:base-global:0.1-SNAPSHOT"
}
Below the buildscript section, a few variables are defined. They are used in the script later.
cuba
The CUBA-specific build logic is encapsulated in the cuba Gradle plugin. It is included in the root of the
script and in the configure section of all modules by the following statement:
apply(plugin: 'cuba')
ide {
copyright = '...'
classComment = '...'
vcs = 'Git'
}
}
artifact - this section defines the group and version of the project artifacts being built. Artifact
names are based on module names specified in settings.gradle.
group - artifact group.
version - artifact version.
isSnapshot - if true, artifact names will have the SNAPSHOT suffix.
tomcat - this section defines the settings of the Tomcat server which is used for fast deployment.
dir - location of the Tomcat installation directory.
port - listening port; 8080 by default.
debugPort - Java debug listening port; 8787 by default.
shutdownPort - port listening to the SHUTDOWN command; 8005 by default.
ajpPort - AJP connector port; 8009 by default.
ide - this section contains instructions for Studio and IDE.
vcs - a version control system for the project. Only Git and svn are currently supported.
copyright - copyright text to be inserted into beginning of each source file.
classComment - comment text to be placed above class declarations in Java source files.
uploadRepository - this section defines the settings of the repository where assembled project
artifacts will be uploaded to upon completion of the uploadArchives task.
url - the repository URL. If not specified, Haulmont’s repository is used.
user - the repository user.
password - the repository password.
You can pass the upload repository parameters from the command line with the following
arguments:
gradlew uploadArchives -PuploadUrl=http://myrepo.com/content/repositories
/snapshots -PuploadUser=me -PuploadPassword=mypassword
configure
The configure sections contain configuration of modules. The most important part of the configuration
is the declaration of dependencies. For example:
configure(coreModule) {
dependencies {
// standard dependencies using variables defined in the script above
compile(globalModule)
provided(servletApi)
jdbc(hsql)
testRuntime(hsql)
// add a custom repository-based dependency
compile('com.company.foo:foo:1.0.0')
// add a custom file-based dependency
compile(files("${rootProject.projectDir}/lib/my-library-0.1.jar"))
// add all JAR files in the directory to dependencies
compile(fileTree(dir: 'libs', include: ['*.jar']))
}
Non-standard module dependencies can be specified in Studio on the Project properties > Advanced
tab. See Studio context help for details.
5.3.2.1. enhance
enhance – the task of the CubaEnhancing type which performs bytecode enhancement (weaving) of
entity classes. It is declared in the global module.
For example:
task enhance(type: CubaEnhancing)
Optional parameters:
persistenceConfig - allows you to specify the set of persistence.xml files explicitly. If not set, the task
will enhance all persistent entities listed in the *persistence.xml files located in the CLASSPATH.
metadataXml - allows you to specify the metadata.xml project file explicitly. If not set, the task will
enhance all non-persistent entities listed in the *metadata.xml files located in the module source tree.
5.3.2.2. setupTomcat
setupTomcat – the task of the CubaSetupTomcat type which performs installation and initialization of the
local Tomcat server for subsequent fast deployment of the application. This task is automatically added to
the project when you apply the cuba Gradle plugin, so you don’t need to declare it in build.gradle.
Tomcat installation directory is specified by the tomcat.dir property of the cuba section. By default, it is
the project’s build/tomcat subdirectory.
5.3.2.3. deploy
deploy – the task of the CubaDeployment type which performs fast deployment of a module to Tomcat. It
is declared in the core, web and portal modules. Parameters:
appName – name of the web application that will be created from the module. In fact, it is the name of a
subdirectory inside tomcat/webapps.
jarNames – the list of JAR file names (without versions) produced as a result of building a module and
intended to be placed into the WEB-INF/lib catalog of the web application. All other module artifacts
and dependencies will be copied to tomcat/shared/lib.
For example:
task deploy(dependsOn: assemble, type: CubaDeployment) {
appName = 'app-core'
jarNames = ['cuba-global', 'cuba-core', 'app-global', 'app-core']
}
5.3.2.4. deployThemes
deployThemes - the task of the CubaDeployThemeTask type which builds and deploys themes defined in
the project to the running web application deployed by the deploy task. Changes in the themes are applied
without the server restart.
For example:
task deployThemes(type: CubaDeployThemeTask, dependsOn: buildScssThemes) {
}
5.3.2.5. buildWar
buildWar – the task of the CubaWarBuilding type, which builds a WAR file from the application code
and its dependencies. It should be declared in the root of build.gradle. The resulting WAR file(s) are
located in the build/distributions project subdirectory.
Any CUBA application consists of at least two blocks: Middleware and Web Client. So the most natural way
to deploy an application is to create two separate WAR files: one for Middleware and one for Web Client.
This also allows you to scale your application when the number of users grows. However, separate WAR
files contain some duplicated dependencies that increase overall size. Besides, extended deployment
options are often not needed and rather complicate the process. The CubaWarBuilding task can create
both types of WAR files: one per block or single WAR containing both blocks. In the latter case, the
application blocks are loaded into separate class loaders inside one web application.
Task parameters:
appHome – the path to the application home directory. The home directory will contain the
configuration, temporary and work directories of the application.
In the appHome parameter, you can specify an absolute or relative path to the home directory, or a
Java system variable, which should be set at server start. For example: appHome = '/work
/sales_home' or appHome = '${app.home}'.
appProperties - an optional map defining application properties. These properties will be added to
the /WEB-INF/local.app.properties files inside generated WAR.
singleWar - should be set to false for building separate WAR files.
includeJdbcDriver - include JDBC driver which is currently used in the project. false by default.
includeContextXml - include Tomcat context.xml file which is currently used in the project.
false by default.
coreContextXmlPath - the relative path to a file which should be used instead of project’s
context.xml if includeContextXml is set to true.
hsqlInProcess - if set to true, the database URL in context.xml will be modified for HSQL
in-process mode.
coreProject - the Gradle project representing the core module (Middleware). If not defined, the
standard core module is used.
webProject - the Gradle project representing the web module (Web Client). If not defined, the
standard web module is used.
portalProject - the Gradle project representing the portal module (Web Portal). Set this property if
the application project contains the portal module. For example, portalProject =
project(':app-portal').
The following parameters should be specified in addition to the ones described above:
<!--Application components-->
<context-param>
<param-name>appComponents</param-name>
<param-value>com.haulmont.cuba</param-value>
</context-param>
<context-param>
<description>List of app properties files for Web Client</description>
<param-name>appPropertiesConfigWeb</param-name>
<param-value>
classpath:cuba-web-app.properties
classpath:web-app.properties
/WEB-INF/local.app.properties
</param-value>
</context-param>
<context-param>
<description>Web resources version for correct caching in
browser</description>
<param-name>webResourcesTs</param-name>
<param-value>${webResourcesTs}</param-value>
</context-param>
<context-param>
<description>List of app properties files for Middleware</description>
<param-name>appPropertiesConfigCore</param-name>
<param-value>
classpath:cuba-app.properties
classpath:app.properties
/WEB-INF/local.app.properties
</param-value>
</context-param>
<!-- Servlet context listeners that load the application blocks -->
<listener>
<listener-
class>com.vaadin.server.communication.JSR356WebsocketInitializer</listener-class>
</listener>
<listener>
<listener-
class>com.haulmont.cuba.core.sys.singleapp.SingleAppCoreServletListener</listener-
class>
</listener>
<listener>
<listener-
class>com.haulmont.cuba.web.sys.singleapp.SingleAppWebServletListener</listener-class>
</listener>
</web-app>
Single WAR contains only core and web modules (Middleware and Web Client). To deploy the portal
module, use separate WAR files.
See also WAR deployment to Jetty section for step-by-step instructions on some variants of WAR
deployment.
5.3.2.6. createDb
createDb – the task of the CubaDbCreation type which creates application database by executing the
corresponding scripts. It is declared in the core module. Parameters:
dbms – the DBMS type, specified as the string hsql, postgres, mssql, or oracle.
dbName – the database name.
dbUser – the DBMS username.
dbPassword – the DBMS user password.
host – the DBMS host and port (optional) in the host[:port] format. If not specified, localhost is
used.
connectionParams - an optional connection parameters string which will be appended to the end of
the connection URL.
masterUrl – the URL used to connect when creating the database. If not specified, the default value
that depends on the DBMS type and the host parameter is used.
dropDbSql – the SQL command to delete the database. If not specified, the default value that depends
on the DBMS type is used.
createDbSql – the SQL command to create a database. If not specified, the default value that
depends on the DBMS type is used.
driverClasspath – the list of JAR files containing the JDBC driver. The items in the list are separated
by ":" on Linux and by ";" on Windows. If not specified, the system uses the dependencies that are part
of the current module’s jdbc configuration. Explicit definition of driverClasspath is necessary when
using Oracle, because its JDBC driver is not available in the dependencies.
oracleSystemPassword – the SYSTEM user password for Oracle.
dbPassword = 'saPass1'
connectionParams = ';instance=myinstance'
}
5.3.2.7. updateDb
updateDb – the task of the CubaDbUpdate type which updates the database by executing the
corresponding scripts. It is similar to the createDb task, except that the dropDbSql and createDbSql
parameters are omitted.
5.3.2.8. startDb
startDb – the task of the CubaHsqlStart type which starts the local HSQLDB server. Parameters:
For example:
task startDb(type: CubaHsqlStart) {
dbName = 'sales'
}
5.3.2.9. stopDb
stopDb – the task of the CubaHsqlStop type which stops the local HSQLDB server. The parameters are
similar to startDb.
5.3.2.10. start
start – the task of the CubaStartTomcat type which starts the local Tomcat server installed by the
setupTomcat task. This task is automatically added to the project when you add the cuba plugin, so you
don’t need to declare it in build.gradle.
5.3.2.11. stop
stop – the task of CubaStopTomcat type which stops the local Tomcat server installed by the
setupTomcat task. This task is automatically added to the project when you include the cuba plugin, so you
don’t need to declare it in build.gradle.
5.3.2.12. restart
restart – the task that stops the local Tomcat server, runs fast deployment, and starts the server once
again.
5.3.2.13. buildWidgetSet
buildWidgetSet - the task of the CubaWidgetSetBuilding which builds a custom GWT widgetset if
the web-toolkit module exists in the project. This module enables development of custom visual
components.
Available parameters:
style - the script output style: OBF, PRETTY or DETAILED. OBF by default.
logLevel - the logging level: ERROR, WARN, INFO, TRACE, DEBUG, SPAM, or ALL. INFO by default.
draft - compile quickly with minimal optimizations. false by default.
Example usage:
task buildWidgetSet(type: CubaWidgetSetBuilding) {
widgetSetClass = 'com.company.sample.web.toolkit.ui.AppWidgetSet'
style = 'PRETTY'
}
5.3.2.14. debugWidgetSet
debugWidgetSet - the task of the CubaWidgetSetDebug type which launches GWT Code Server for
debugging widgets in the browser.
Example usage:
task debugWidgetSet(type: CubaWidgetSetDebug) {
widgetSetClass = 'com.company.sample.web.toolkit.ui.AppWidgetSet'
}
Ensure that the web-toolkit module has a dependency on Servlet API library in the runtime
configuration:
configure(webToolkitModule) {
dependencies {
runtime(servletApi)
}
...
See Debugging Web Widgets for information on how to debug code in the browser.
5.3.2.15. zipProject
zipProject is the task of the CubaZipProject type which creates a ZIP archive of your project. The
archive will not contain IDE project files, build results and Tomcat server. But HSQL database is included to
the archive if present in the build directory.
This task is automatically added to the project when you apply the cuba Gradle plugin, so you don’t need to
declare it in build.gradle.
If you are working with the project in CUBA Studio, all commands under the Build and Run main menu
items actually connect to the Gradle daemon (launched at the start of Studio server) and run
corresponding tasks.
Alternatively, you can use the executable gradlew script (Gradle wrapper) included in the project. The
script should be located in the project root directory and can be created in Studio using the Build >
Create Gradle wrapper command.
One more way is to use the manually installed Gradle version 2.13. In this case, run the gradle
executable located in the bin subdirectory of the Gradle installation.
It is recommended to run the gradlew and gradle commands with the --daemon argument; in this
case the Gradle daemon is retained in memory, which significantly accelerates the subsequent
execution.
To remove the daemon from memory, you can use the --stop argument.
For example, in order to compile the Java files and build the JAR files for project artifacts, you need to run
the following command:
gradlew --daemon assemble
If your project uses Premium Add-ons, and you are starting build tasks outside Studio, pass the
Premium Add-ons repository credentials in the command line arguments with -P prefix, for example:
gradle assemble -PpremiumRepoUser=111111222222 -PpremiumRepoPass=abcdefabcdef
Typical build tasks in their normal usage sequence are provided below.
idea, eclipse – create IntelliJ IDEA or Eclipse project files. When this task is executed, dependencies
with their source code are loaded from the artifact repository to the local Gradle cache.
cleanIdea, cleanEclipse – remove IntelliJ IDEA or Eclipse project files.
assemble – compile Java files and build JARs for project artifacts in the build subdirectories of the
modules.
Once the project is created, you can keep developing it in the Studio, or create IntelliJ IDEA or Eclipse
project files and open the project in the IDE.
5.5. Logging
The platform uses Logback framework for logging.
To output to the log, use SLF4J API: get a logger for the current class and invoke one of its methods, for
example:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logs for the Middleware, Web Client and Web Portal blocks are configured at the application server level; in
fast deployment mode the server is Tomcat. Logs for the Desktop Client block are configured separately.
Among other things, the setenv.* files define loading parameters for the logback.xml configuration file
using the CATALINA_OPTS variable.
logback.xml defines logging configuration. The file has the following structure:
appender elements define the "output device" for the log. The main appenders are FILE and CONSOLE.
The level parameter of ThresholdFilter defines the message threshold. By default, it is DEBUG for
a file and INFO for console. It means that ERROR, WARN, INFO and DEBUG messages are written to a file,
while ERROR, WARN and INFO are written to console.
The path to the log file for the file appender is defined in the file parameter. The default is
tomcat/logs/app.log.
logger elements define the logger parameters that are used to print messages from the program code.
Logger names are hierarchical, i.e. the settings of the com.company.sample logger have effect on the
com.company.sample.core.CustomerServiceBean and
com.company.sample.web.CustomerBrowse loggers, if the loggers do not explicitly override the
settings with their own.
Minimum logging level is defined by the level attribute. For example, if the category is INFO, then
DEBUG and TRACE messages will not be logged. It should be kept in mind that message logging is also
affected by the level threshold set in the appender.
You can quickly change logger levels and appender thresholds for a running server using the *
Administration* > Server log screen available in the web client. Any changes to the logging settings are
effective only during server runtime and are not saved to a file. The screen also enables viewing and loading
log files from the server logs folder (tomcat/logs).
The platform automatically adds the following information to the messages written to the file log:
application – the name of the web application that has logged the message. This information enables
identifying messages from different application blocks (Middleware, Web Client), since they are written
into the same file.
user – the login name of the user who invoked the code logging the message. This helps to track activity
of a certain user in the log. If the code that logged a message was not invoked within a specific user
session, the user information is not added.
For example, the following message has been written to the log by the code of the Middleware block
(app-core), running under the admin user:
16:12:20.498 DEBUG [http-nio-8080-exec-7/app-core/admin]
com.haulmont.cuba.core.app.DataManagerBean - loadList: ...
Create a new file, for example sample-logback.xml, in the src directory of the desktop module, and
copy the contents of cuba-logback.xml to this new file. cuba-logback.xml file is located in one of
the platform’s JAR files and can be easily found using search in the IDE.
Define path to a log file in the file parameter of the FILE appender.
Add settings for loggers of your project.
In the inheritor class of com.haulmont.cuba.desktop.App of your project, for example SampleApp,
override the getDefaultLogConfig() method and use it to return the path to your log file relative to
the CLASSPATH root. For example:
public class SampleApp extends App {
...
@Override
protected String getDefaultLogConfig() {
return "sample-logback.xml";
}
If necessary, you can override the location of the configuration file at the application start using
logback.configurationFile system property.
5.6. Debugging
This section explains how to use step-by-step debugging in CUBA applications.
gradlew start
After this, the server will accept debugger connections over port 8787. Port number can be changed in the
bin/setenv.* file, in the JPDA_OPTS variable.
For debugging in Intellij IDEA you need to create a new Remote type Run/Debug Configuration element
in the application project and set its Port property to 8787.
6. After changing the Java code in the web-toolkit module, refresh the web page in the browser. The
widgetset will be rebuilt incrementally in approximately 8-10 seconds.
5.7. Testing
This section covers the ways of testing CUBA applications on different layers.
@Named("itemsTable.add")
protected AddAction addAction;
@Override
public void init(Map<String, Object> params) {
addAction.setWindowId("sales$Product.browse");
addAction.setHandler(new Lookup.Handler() {
@Override
public void handleLookup(Collection items) {
// some code
}
});
}
}
You can write the following test checking the init() method:
public class OrderEditorTest {
OrderEditor editor;
@Mocked
Window.Editor frame;
@Mocked
AddAction addAction;
@Before
public void setUp() throws Exception {
editor = new OrderEditor();
editor.setWrappedFrame(frame);
editor.addAction = addAction;
}
@Test
public void testInit() {
editor.init(Collections.<String, Object>emptyMap());
editor.setItem(new Order());
new Verifications() {
{
addAction.setWindowId("sales$Product.browse");
addAction.setHandler(withInstanceOf(Window.Lookup.Handler.class));
}
};
}
}
First, create the test directory in your core module next to the src directory. Re-create IDE project files to
be able to run tests from the IDE.
The platform contains the TestContainer class which can be used as a base class for the test container
in the application project. Create a subclass in the test directory of your core module and, in its
constructor, redefine parameters for loading components and application properties and test database
connection parameters. For example:
public class SalesTestContainer extends TestContainer {
public SalesTestContainer() {
super();
appComponents = new ArrayList<>(Arrays.asList(
"com.haulmont.cuba"
We recommend using a separate test database, which can be created, for example, by the following Gradle
task defined in build.gradle:
configure(coreModule) {
...
task createTestDb(dependsOn: assemble, description: 'Creates local Postgres database
for tests', type: CubaDbCreation) {
dbms = 'postgres'
dbName = 'sales_test'
dbUser = 'cuba'
dbPassword = 'cuba'
}
The test container should be used in test classes as a JUnit rule specified by the @ClassRule annotation:
public class CustomerLoadTest {
@ClassRule
public static SalesTestContainer cont = new SalesTestContainer();
@Before
public void setUp() throws Exception {
customer = cont.persistence().createTransaction().execute(em -> {
Customer customer = new Customer();
customer.setName("testCustomer");
em.persist(customer);
return customer;
});
@After
public void tearDown() throws Exception {
cont.deleteRecord(customer);
}
@Test
public void test() {
try (Transaction tx = cont.persistence().createTransaction()) {
EntityManager em = cont.persistence().getEntityManager();
TypedQuery<Customer> query = em.createQuery(
"select c from sales$Customer c", Customer.class);
List<Customer> list = query.getResultList();
tx.commit();
assertTrue(list.size() > 0);
}
}
}
In the example above, the test container is initialized once for all test methods of this class, and disposed
after all of them finished.
As the container startup takes some time, you may want to initialize the container once for all tests
contained in several test classes. In this case, create a common singleton instance of your test container:
public class SalesTestContainer extends TestContainer {
public SalesTestContainer() {
...
}
private Common() {
}
@Override
public void before() throws Throwable {
if (!initialized) {
super.before();
initialized = true;
}
setupContext();
}
@Override
public void after() {
cleanupContext();
// never stops - do not call super
}
}
}
@ClassRule
public static SalesTestContainer cont = SalesTestContainer.Common.INSTANCE;
...
}
The TestContainer class contains the following methods that can be used in the test code (see the
CustomerLoadTest example above):
Client integration test class should be inherited from CubaClientTestCase. In the @Before method, you
should call the inherited methods addEntityPackage(), setViewConfig() and then
setupInfrastructure() to create Metadata and Configuration objects and deploy metadata for
selected entities. Then, in the @Before method, you can extend the infrastructure with required mock
objects using Expectations or NonStrictExpectations.
metadataSession = metadata.getSession();
dataSupplier = new TestDataSupplier();
dataSupplier.commitCount = 0;
new NonStrictExpectations() {
@Mocked ClientConfig clientConfig;
DataManager EntityManager
DataManager is available on both middle and EntityManager is available only on the middle tier.
client tiers.
DataManager defines a few high-level methods for EntityManager mostly resembles the standard
working with detached entities: load(), javax.persistence.EntityManager.
loadList(), reload(), commit().
DataManager always starts new transactions You have to open a transaction before working
internally. with EntityManager.
DataManager loads partial entities according to EntityManager always loads all local attributes. If a
views. view is used, it affects only reference attributes.
DataManager queries can return entities only. EntityManager can run any JPQL or native
Queries for single attributes or aggregates (SUM, queries.
COUNT) will fail.
DataManager applies all security restrictions when EntityManager does not impose security
invoked on the client tier. restrictions.
When you work with data on the client tier, you have only one option - DataManager. On the middleware,
use EntityManager when you need to implement some atomic logic inside a transaction or run a query
returning single attributes or aggregates. Otherwise, on the middleware you can use both.
If you need to overcome restrictions of DataManager when working on the client tier, create your own
service and use EntityManager to work with data. In the service, you can check permissions using the
Security interface and return data to the client in the form of persistent or non-persistent entities or arbitrary
values.
In screen XML-descriptors, component attributes for displaying static text (such as caption) can address
localized messages using the rules of MessageTools.loadString() method. For example:
caption="msg://roleName" – gets a message defined by the roleName key in the message
pack of the current screen. Screen message pack is defined by the messagesPack attribute of the
root window element.
caption="msg://com.company.sample.entity/Role.name" – gets a message defined by
the Role.name key in the com.company.sample.entity message pack.
In screen controllers, localized strings can be retrieved in the following ways:
From the current screen message pack:
Using getMessage() method inherited from the AbstractFrame base class. For example:
String msg = getMessage("warningMessage");
Using formatMessage() method inherited from the AbstractFrame base class. In this case,
the extracted message is used to format submitted parameters according to the rules of
String.format() method. For example:
messages.properties:
warningMessage = Invalid email address: '%s'
Java controller:
String msg = formatMessage("warningMessage", email);
From an arbitrary messages pack via an injection of Messages infrastructure interface. For example:
@Inject
private Messages messages;
@Override
public void init(Map<String, Object> params) {
String msg = messages.getMessage(getClass(), "warningMessage");
...
}
For components managed by a Spring container (managed beans, services, JMX-beans, Spring MVC
controllers of the portal module), localized messages can be retrieved with the help of the Messages
infrastructure interface injection:
@Inject
protected Messages messages;
...
String msg = messages.getMessage(getClass(), "warningMessage");
In application code where injection is not possible, the Messages interface can be obtained using the
static get() method of the AppBeans class:
protected Messages messages = AppBeans.get(Messages.class);
...
String msg = messages.getMessage(getClass(), "warningMessage");
Additionally, a specific initialization method with a @PostConstruct annotation can be created in the entity
class. In this case, any global infrastructure interfaces and beans can be invoked during initialization, for
example:
public class MyEntity extends StandardEntity {
...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "USER_ID")
protected User creator;
...
@PostConstruct
protected void init() {
setCreator(AppBeans.get(UserSessionSource.class).getUserSession().getUser());
}
}
A fragment of a screen XML descriptor showing the lists of two entities simultaneously:
<dsContext>
<collectionDatasource id="typesDs"
class="com.haulmont.sample.entity.DeviceType"
view="_local">
<query>
select e from sample$DeviceType e
</query>
</collectionDatasource>
<collectionDatasource id="descriptionsDs"
class="com.haulmont.sample.entity.DeviceDescription"
view="_local">
<query>
select e from sample$DeviceDescription e where e.deviceType.id = :ds$typesDs
</query>
</collectionDatasource>
</dsContext>
<layout>
...
<table id="typeTable">
<actions>
<action id="create"/>
<action id="edit"/>
<action id="remove"/>
</actions>
<columns>
<column id="name"/>
</columns>
<rows datasource="typesDs"/>
</table>
...
<table id="descriptionTable">
<actions>
<action id="create"/>
<action id="edit"/>
<action id="remove"/>
</actions>
<columns>
<column id="description"/>
</columns>
<rows datasource="descriptionsDs"/>
</table>
</split>
</layout>
@Inject
private CollectionDatasource<DeviceType, UUID> typesDs;
@Named("descriptionTable.create")
private CreateAction descrCreateAction;
@Override
public void init(Map<String, Object> params) {
typesDs.addItemChangeListener(event -> {
descrCreateAction.setInitialValues(Collections.<String,
Object>singletonMap("deviceType", event.getItem()));
});
}
}
A listener is added in the controller for selected record change event in the typesDs datasource. When the
selected record is changed, the system invokes the action’s setInitialValues() method and submits a
map with one element whose key is the attribute name (deviceType) and value (the selected instance of
DeviceType). Thus, during the execution of CreateAction, the deviceType attribute of the new
DeviceDescription instance will contain the instance of the DeviceType that was selected in the table.
Assume we have the following task: a project has an Employee entity that should be linked to a platform
entity (User). When a new employee instance is being created, a new user instance should be created as
well.
To achieve this, we declare the data source for the employee instance and the nested data source for the
linked user in the employee edit screen XML descriptor:
<dsContext>
<datasource id="employeeDs"
class="com.haulmont.sample.entity.Employee"
view="employee-edit">
<datasource id="userDs"
property="user"/>
</datasource>
</dsContext>
@Override
protected void initNewItem(Employee item) {
User user = metadata.create(User.class);
user.setGroup(defaultGroup);
final UserRole userRole = metadata.create(UserRole.class);
userRole.setUser(user);
userRole.setRole(defaultRole);
getDsContext().addBeforeCommitListener(context -> {
context.getCommitInstances().add(userRole);
});
item.setUser(user);
}
Here, in the initNewItem() method, the new User instance is created and assigned the defaultGroup.
Association with the defaultRole is set up using the new instance of UserRole entity. To save this
relationship to the DB during screen commit the UserRole instance is added to the saved entities collection
in the beforeCommit() method of DsContext.CommitListener.
The new instance of User is assigned to the corresponding attribute of the edited Employee entity and is
thus included in the nested data source userDs. This gives us an opportunity to edit necessary user
attributes in the employee screen and also leads to automatic saving of the user instance in the same
transaction with the other entities when the screen is committed.
Typically, the entities belonging to a composition are edited together since it is more convenient. For
example, a user opens the airport editing screen and sees the list of terminals, so he can create and edit
them, but all changes both for the airport and the terminals are saved to the database together in one
transaction, and only after the user confirms saving of the master entity (the airport).
2. The Airport entity contains one-to-many collection of terminals. The corresponding field is annotated
with @Composition in order to implement composition, and @OnDelete for cascaded soft delete:
@Entity(name = "sample$Airport")
@Table(name = "SAMPLE_AIRPORT")
public class Airport extends StandardEntity {
...
@OneToMany(fetch = FetchType.LAZY, mappedBy = "airport")
@OnDelete(DeletePolicy.CASCADE)
@Composition
protected List<Terminal> terminals;
3. The view of the airport editing screen should contain the terminals attributes collection:
<view entity="sample$Airport" name="airport-edit" extends="_local">
<property name="terminals" view="_local"/>
</view>
For the Terminal entity, we are using the _local view, although it contains the airport link attribute
(a link to an airport). The airport attribute is set only at the creation of a new Terminal instance and
5. Define a table displaying terminals and standard actions for it in the XML descriptor of the airport editor:
<table id="terminalsTable">
<actions>
<action id="create"/>
<action id="edit"/>
<action id="remove"/>
</actions>
<buttonsPanel>
<button action="terminalsTable.create"/>
<button action="terminalsTable.edit"/>
<button action="terminalsTable.remove"/>
</buttonsPanel>
<columns>
<column id="code"/>
<column id="name"/>
<column id="address"/>
</columns>
<rows datasource="terminalsDs"/>
</table>
6. It is sufficient to define the standard elements in the terminal editor: datasource for the Terminal
instance and visual components related to this datasource for editing terminal attributes.
The Terminal entity contains the meetingPoints attribute – a collection of the MeetingPoint
instances. In order for all three entities to become a single composition and be edited together, you should
do the following in addition to the steps described above:
1. Mark the meetingPoints attribute of the Terminal class as @Composition and @OnDelete
similarly to the terminals attribute of the Airport class.
2. Create a new view for the Terminal:
<view entity="sample$Terminal" name="terminal-edit" extends="_local">
<property name="meetingPoints" view="_local"/>
</view>
3. Define datasources for an instance of the Airport and nested entities for the entire composition depth
in the Airport edit screen XML descriptor:
<dsContext>
<datasource id="airportDs"
class="com.haulmont.sample.entity.Airport"
view="airport-edit">
<collectionDatasource id="terminalsDs" property="terminals">
<collectionDatasource id="meetingPointsDs" property="meetingPoints"/>
</collectionDatasource>
</datasource>
</dsContext>
Here, the meetingPointsDs datasource is not associated with any visual components, however it is
needed for correct operation of joint editing of the composition.
4. Define the nested data source and a corresponding table for the meetingPoints collection in the
terminal edit screen XML descriptor.
As a result, the updated instances of the MeetingPoint, as well as the Terminal instances, will be saved
to the database only with the Airport instance in the same transaction.
Assume we have the following task: a project has an Employee entity that should be linked one-to-one to a
platform entity (User).
If the name attribute of the User entity is changed, for example, through a standard user management
screen, the name attribute of the related Employee should change as well. This is a common task for
"denormalized" data, which is typically solved using entity listeners. Our case is more complex, since we
need to track changes of the platform’s User entity, and thus we cannot add an entity listener using
@Listeners annotation. However, we can add a listener dynamically using the EntityListenerManager
bean, and it is better to do this on application start.
Let us create the AppLifecycle bean implementing the AppContext.Listener interface in the
application core module and register it by invoking AppContext.addListener() method in the object
constructor:
@Component("sample_AppLifecycle")
public class AppLifecycle implements AppContext.Listener {
@Inject
private EntityListenerManager entityListenerManager;
public AppLifecycle() {
AppContext.addListener(this);
}
@Override
public void applicationStarted() {
entityListenerManager.addListener(User.class, UserEntityListener.class);
}
@Override
public void applicationStopped() {
}
}
}
}
}
}
As a result, the applicationStarted() method of this bean will be invoked immediately after the start of
the Middleware block. In this method, the internal UserEntityListener class is registered as an entity
listener for the User entity.
The onBeforeUpdate() method of the UserEntityListener class will be invoked every time before
the changes in the User instances are saved to the database. The method checks if the name attribute
exists among the updated attributes. If yes, a related Employee instance is loaded and its name is updated
with the new value.
A view for loading an Employee together with FileDescriptor should include all local attributes of
FileDescriptor:
<view class="com.company.sample.entity.Employee"
name="employee-edit">
<property name="name"/>
...
<property name="imageFile"
view="_local">
</property>
</view>
Components used to display, upload and download images are contained within the groupBox container. Its
top part shows a picture using the embedded component, while its bottom part from left to right contains the
upload component and buttons to download and clear the image. As a result, this part of the screen should
look like this:
@Inject
private DataSupplier dataSupplier;
@Inject
private FileStorageService fileStorageService;
@Inject
@Inject
private Embedded embeddedImage;
@Inject
private FileUploadField uploadField;
@Inject
private Button downloadImageBtn;
@Inject
private Button clearImageBtn;
@Inject
private Datasource<Employee> employeeDs;
@Override
public void init(Map<String, Object> params) {
uploadField.addFileUploadSucceedListener(event -> {
FileDescriptor fd = uploadField.getFileDescriptor();
try {
fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd);
} catch (FileStorageException e) {
throw new RuntimeException("Error saving file to FileStorage", e);
}
getItem().setImageFile(dataSupplier.commit(fd));
displayImage();
});
uploadField.addFileUploadErrorListener(event ->
showNotification("File upload error", NotificationType.HUMANIZED));
employeeDs.addItemPropertyChangeListener(event -> {
if ("imageFile".equals(event.getProperty()))
updateImageButtons(event.getValue() != null);
});
}
@Override
protected void postInit() {
displayImage();
updateImageButtons(getItem().getImageFile() != null);
}
getItem().setImageFile(null);
displayImage();
}
The init() method first initializes the uploadField component that is used for uploading new
images. In the case of a successful upload, a new FileDescriptor instance is retrieved from the
component and the corresponding files are sent from the temporary client storage to FileStorage by
@Inject
protected EmailService emailService;
@Override
protected boolean postCommit(boolean committed, boolean close) {
if (committed && justCreated) {
// If a new entity was saved to the database, ask a user about sending an
email
showOptionDialog(
"Email",
"Send the news item by email?",
MessageType.CONFIRMATION,
new Action[] {
new DialogAction(DialogAction.Type.YES) {
@Override
public void actionPerform(Component component) {
sendByEmail();
}
},
new DialogAction(DialogAction.Type.NO)
}
);
}
return super.postCommit(committed, close);
}
As you can see, the sendByEmail() method invokes the EmailService and passes the EmailInfo
instance describing the the messages. The body of the messages will be created on the basis of the
news_item.txt template.
2. Create the body template file news_item.txt in the com.company.demo.templates package of
the core module:
The company news:
${newsItem.content}
This is a Freemarker template which will use parameters passed in the EmailInfo instance
(newsItem in this case).
3. Launch the application, open the NewsItem entity browser and click Create. The editor screen will be
opened. Fill in the fields and press OK. The confirmation dialog with the question about sending emails
Restart the application server. The scheduling mechanism is now active and invokes the email queue
processing.
7. Go to the Administration > Email History screen. The status of the emails will be Sent if they were
successfully sent, or, most probably, Sending or Queue otherwise. In the latter case, you can open the
application log in build/tomcat/logs/app.log and find out the reason. The email sending
mechanism will take several (10 by default) attempts to send the messages and if they fail, set the
status to Not sent.
8. The most obvious reason that emails cannot be sent is that you have not set up the SMTP server
parameters. You can set the parameters in the database through the
app-core.cuba:type=Emailer JMX bean or in the application properties file of your middleware.
Let us consider the latter. Open the modules/core/src/app.properties file and add the required
parameters:
cuba.email.fromAddress = do-not-reply@company.com
cuba.email.smtpHost = mail.company.com
Restart the application server. Go to Administration > JMX Console, find the Emailer JMX bean and
try to send a test email to yourself using the sendTestEmail() operation.
9. Now your sending mechanism is set up correctly, but it will not send the messages in the Not sent
state. So you have to create another NewsItem in the editor screen. Do it and then watch how the
status of new messages in the Email History screen will change to Sent.
1. Integrate a Vaadin add-on. Many third-party Vaadin components are distributed as add-ons and
available at https://vaadin.com/directory.
2. Integrate a JavaScript component. You can create a Vaadin component using a JavaScript library.
3. Create a new Vaadin component with the client part written on GWT.
Futher on, you can integrate the resulting Vaadin component into CUBA Generic UI to be able to use it
declaratively in screen XML descriptors and bind to datasources.
And the final step of integration is the support of the new component in the Studio WYSIWYG layout editor.
This section gives you examples of creating new visual components with all the methods described above.
Integration to the Generic UI and support in Studio are the same for all methods, so these topics are
described only for a new component created on the basis of a Vaadin add-on.
A Vaadin add-on may be integrated if the application project has a web-toolkit module. Create the module
by clicking the Create web toolkit module link of the Project properties navigator section.
Then click the Create new UI component link. The New UI component page will open. Select the Vaadin
add-on value in the Component type section.
The Add-on Maven dependency field contains Maven coordinates of the Vaadin add-on. The add-on
will be included as a dependency to the project. You can define coordinates in two formats:
1. As an XML copied from the add-on web site (http://vaadin.com/addon/stepper):
<dependency>
<groupId>org.vaadin.addons</groupId>
<artifactId>stepper</artifactId>
<version>2.2.2</version>
</dependency>
The Inherited widgetset field contains a widgetset name of the add-on. In our case it is
org.vaadin.risto.stepper.widgetset.StepperWidgetset.
Integrate into generic UI - deselect this checkbox as we do not integrate the component into the
Generic UI in this example.
If you open the project in the IDE, you can see that Studio has changed two files:
1. In build.gradle, the web module now contains a dependency on the add-on that contains the
component.
configure(webModule) {
...
dependencies {
...
compile("org.vaadin.addons:stepper:2.2.2")
}
2. The AppWidgetSet.gwt.xml file of the web-toolkit module now inherits the add-on widgetset:
<module>
<inherits name="com.haulmont.cuba.web.toolkit.ui.WidgetSet" />
You can speed up the widgetset compilation by defining the user.agent property. In this
example, widgetset will be compiled only for browsers based on WebKit: Chrome, Safari, etc.
Now the component from the Vaadin add-on is included to the project. Let’s see how to use it in the project
screens.
Screens that use Vaadin components directly must be placed in the web module.
Actually, screens can be placed in the gui module as well, but then the code that uses the Vaadin
component should be moved to a separate companion.
Next, we will add the stepper component to the screen. You can place it in a FieldGroup or in a
separate container. Let’s examine both methods.
1. Add the custom = "true" attribute to the score field of the fieldGroup component of the
customer-edit.xml screen.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
caption="msg://editCaption"
class="com.company.addondemo.web.customer.CustomerEdit"
datasource="customerDs"
focusComponent="fieldGroup"
messagesPack="com.company.addondemo.web.customer">
<dsContext>
<datasource id="customerDs"
class="com.company.addondemo.entity.Customer"
view="_local"/>
</dsContext>
<layout expand="windowActions" spacing="true">
<fieldGroup id="fieldGroup" datasource="customerDs">
<column width="250px">
<field id="name"/>
<field id="score" custom="true"/>
</column>
</fieldGroup>
import com.company.addondemo.entity.Customer;
import com.haulmont.cuba.gui.components.AbstractEditor;
import com.haulmont.cuba.gui.components.Component;
import com.haulmont.cuba.gui.components.FieldGroup;
import com.haulmont.cuba.gui.components.VBoxLayout;
import com.haulmont.cuba.gui.xml.layout.ComponentsFactory;
import com.haulmont.cuba.web.gui.components.WebComponentsHelper;
import com.vaadin.ui.Layout;
import org.vaadin.risto.stepper.IntStepper;
import javax.inject.Inject;
import java.util.Map;
@Inject
private ComponentsFactory componentsFactory;
@Inject
private FieldGroup fieldGroup;
@Override
public void init(Map<String, Object> params) {
fieldGroup.addCustomField("score", (datasource, propertyId) -> {
Component box = componentsFactory.createComponent(VBoxLayout.class);
Layout layout = (Layout) WebComponentsHelper.unwrap(box);
layout.addComponent(stepper);
stepper.setSizeFull();
stepper.addValueChangeListener(event ->
datasource.getItem().setValue(propertyId,
event.getProperty().getValue()));
return box;
});
}
@Override
protected void initNewItem(Customer item) {
item.setScore(0);
}
@Override
protected void postInit() {
stepper.setValue(getItem().getScore());
}
}
The init() method initializes the custom score field. The ComponentsFactory creates an
instance of BoxLayout, retrieves a link to the Vaadin container via WebComponentsHelper, and adds
the new component to it. The BoxLayout is then returned to be used in the custom field.
Data binding is implemented programmatically by setting a current value to the stepper component
from the edited Customer instance in the postInit() method. Additionally, the corresponding
entity attribute is updated through the value change listener, when the user changes the value.
2. The new component can be used in any part of the screen outside of the FieldGroup. In order to do
this, declare the scoreBox container in the XML-descriptor:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
caption="msg://editCaption"
class="com.company.addondemo.web.customer.CustomerEdit"
datasource="customerDs"
focusComponent="fieldGroup"
messagesPack="com.company.addondemo.web.customer">
<dsContext>
<datasource id="customerDs"
class="com.company.addondemo.entity.Customer"
view="_local"/>
</dsContext>
<layout expand="windowActions" spacing="true">
<fieldGroup id="fieldGroup" datasource="customerDs">
<column width="250px">
<field id="name"/>
</column>
</fieldGroup>
Inject the container to the screen controller, retrieve a link to the underlying Vaadin container and add
the component to it:
package com.company.addondemo.web.customer;
import com.company.addondemo.entity.Customer;
import com.haulmont.cuba.gui.components.AbstractEditor;
import com.haulmont.cuba.gui.components.BoxLayout;
import com.haulmont.cuba.web.gui.components.WebComponentsHelper;
import com.vaadin.ui.Layout;
import org.vaadin.risto.stepper.IntStepper;
import javax.inject.Inject;
import java.util.Map;
@Inject
private BoxLayout scoreBox;
@Override
public void init(Map<String, Object> params) {
com.vaadin.ui.Layout box = (Layout) WebComponentsHelper.unwrap(scoreBox);
box.addComponent(stepper);
stepper.addValueChangeListener(event ->
getItem().setValue("score", event.getProperty().getValue()));
}
@Override
protected void postInit() {
stepper.setValue(getItem().getScore());
}
}
Start the application server. The resulting editor screen will look as follows:
Create a new project in CUBA Studio and name it addon-gui-demo. Type agd in the Project namespace
field.
Create the web-toolkit module by clicking the Create web toolkit module link of the Project properties
navigator section.
Then click the Create new UI component link. The New UI component page will open. Select the Vaadin
add-on value in the Component type section.
Fill in the Add-on Maven dependency and Inherited widgetset as described in the previous section.
Integrate into Generic UI - defines that a component should be integrated into the Generic UI.
Component XML element - an element to be used in screen XML descriptors. Enter stepper.
Component interface name - a name of the component Generic UI interface. Enter Stepper.
FQN of the Vaadin component from add-on - fully qualified class name of the Vaadin component from
the add-on. In our case it is org.vaadin.risto.stepper.IntStepper.
information about the new component will be added to the existing file.
cuba-ui-component.xml - the file that registers a new component loader in web module. If the file
already exists, the information about the new component will be added to the existing file.
Open the Stepper interface in the gui module. Replace its content with the following code:
package com.company.addonguidemo.gui.components;
import com.haulmont.cuba.gui.components.Field;
boolean isManualInputAllowed();
void setManualInputAllowed(boolean value);
boolean isMouseWheelEnabled();
void setMouseWheelEnabled(boolean value);
int getStepAmount();
void setStepAmount(int amount);
int getMaxValue();
void setMaxValue(int maxValue);
int getMinValue();
void setMinValue(int minValue);
}
The base interface for the component is Field, which is designed to display and edit an entity attribute.
Open the WebStepper class - a component implementation in the web module. Replace its content with
the following code:
package com.company.addonguidemo.web.gui.components;
import com.company.addonguidemo.gui.components.Stepper;
import com.haulmont.cuba.web.gui.components.WebAbstractField;
import org.vaadin.risto.stepper.IntStepper;
@Override
public boolean isManualInputAllowed() {
return component.isManualInputAllowed();
}
@Override
@Override
public boolean isMouseWheelEnabled() {
return component.isMouseWheelEnabled();
}
@Override
public void setMouseWheelEnabled(boolean value) {
component.setMouseWheelEnabled(value);
}
@Override
public int getStepAmount() {
return component.getStepAmount();
}
@Override
public void setStepAmount(int amount) {
component.setStepAmount(amount);
}
@Override
public int getMaxValue() {
return component.getMaxValue();
}
@Override
public void setMaxValue(int maxValue) {
component.setMaxValue(maxValue);
}
@Override
public int getMinValue() {
return component.getMinValue();
}
@Override
public void setMinValue(int minValue) {
component.setMinValue(minValue);
}
}
The chosen base class is WebAbstractField, which implements the methods of the Field interface.
The StepperLoader class in gui module loads the component from its representation in XML.
package com.company.addonguidemo.gui.xml.layout.loaders;
import com.company.addonguidemo.gui.components.Stepper;
import com.haulmont.cuba.gui.xml.layout.loaders.AbstractFieldLoader;
resultComponent = factory.createComponent(Stepper.class);
loadId(resultComponent, element);
}
@Override
public void loadComponent() {
super.loadComponent();
The AbstractFieldLoader class contains code for loading basic properties of the Field component.
So StepperLoader loads only the specific properties of the Stepper component.
The cuba-ui-component.xml file in the web module registers the new component and its loader.
Leave the file unchanged.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<components>
<component>
<name>stepper</name>
<componentLoader>com.company.addonguidemo.gui.xml.layout.loaders.StepperLoader</componen
tLoader>
<class>com.company.addonguidemo.web.gui.components.WebStepper</class>
</component>
</components>
The ui-component.xsd file in gui module contains XML schema definitions of custom visual
components. Add the stepper attributes definition.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xs:schema xmlns="http://schemas.company.com/agd/0.1/ui-component.xsd"
attributeFormDefault="unqualified"
elementFormDefault="qualified"
targetNamespace="http://schemas.company.com/agd/0.1/ui-component.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="stepper">
<xs:complexType>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="caption" type="xs:string"/>
<xs:attribute name="width" type="xs:string"/>
<xs:attribute name="height" type="xs:string"/>
<xs:attribute name="datasource" type="xs:string"/>
<xs:attribute name="property" type="xs:string"/>
<xs:attribute name="manualInput" type="xs:boolean"/>
<xs:attribute name="mouseWheel" type="xs:boolean"/>
<xs:attribute name="stepAmount" type="xs:int"/>
<xs:attribute name="maxValue" type="xs:int"/>
<xs:attribute name="minValue" type="xs:int"/>
</xs:complexType>
</xs:element>
</xs:schema>
</dsContext>
<layout expand="windowActions" spacing="true">
<fieldGroup id="fieldGroup" datasource="customerDs">
<column width="250px">
<field id="name"/>
</column>
</fieldGroup>
<app:stepper id="stepper" datasource="customerDs" property="score"
caption="Score"
minValue="1" maxValue="20"/>
<frame id="windowActions" screen="editWindowActions"/>
</layout>
</window>
In the example above, the stepper component is associated with the score attribute of the
Customer entity. An instance of this entity is managed by the customerDs datasource.
2. Using the new component inside a FieldGroup:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
caption="msg://editCaption"
class="com.company.addonguidemo.gui.customer.CustomerEdit"
datasource="customerDs"
focusComponent="fieldGroup"
messagesPack="com.company.addonguidemo.gui.customer">
<dsContext>
<datasource id="customerDs"
class="com.company.addonguidemo.entity.Customer"
view="_local"/>
</dsContext>
<layout expand="windowActions" spacing="true">
<fieldGroup id="fieldGroup" datasource="customerDs">
<column width="250px">
<field id="name"/>
<field id="score" custom="true"/>
</column>
</fieldGroup>
<frame id="windowActions" screen="editWindowActions"/>
</layout>
</window>
package com.company.addonguidemo.gui.customer;
import com.company.addonguidemo.entity.Customer;
import com.company.addonguidemo.gui.components.Stepper;
import com.haulmont.cuba.gui.components.AbstractEditor;
import com.haulmont.cuba.gui.components.FieldGroup;
import com.haulmont.cuba.gui.xml.layout.ComponentsFactory;
import javax.inject.Inject;
import java.util.Map;
@Inject
private ComponentsFactory componentsFactory;
@Inject
private FieldGroup fieldGroup;
@Override
public void init(Map<String, Object> params) {
fieldGroup.addCustomField("score", (datasource, propertyId) -> {
Stepper stepper = componentsFactory.createComponent(Stepper.class);
stepper.setDatasource(datasource, propertyId);
stepper.setWidth("100%");
return stepper;
});
}
}
To adapt the component style, create a theme extension in the project. Click the Create theme
extension link in the Project properties navigator section. Select the halo theme. After that, open the
themes/halo/halo-ext.scss file located in the web module and add the following code:
@import "../halo/halo";
Start the application server. The resulting editor screen will look as follows:
Click the Create new UI component button on the Project properties navigator section. The New UI
component page will open. Select the JavaScript component value in the Component type section.
Deselect the Integrate into Generic UI flag. The process of integration into the Generic UI is the same as
described at Integrating a Vaadin Component into the Generic UI, so we won’t repeat it here.
After clicking the OK button Studio will generate the following files:
Let’s examine the generated files and make necessary changes in the source code.
SlideState state class defines what data is transferred between the server and the client. In our case
it is a minimal possible value, maximum possible value and selected values.
package com.company.jscomponent.web.toolkit.ui.slider;
import com.vaadin.shared.ui.JavaScriptComponentState;
import com.vaadin.annotations.StyleSheet;
import com.vaadin.ui.AbstractJavaScriptComponent;
import com.vaadin.annotations.JavaScript;
import elemental.json.JsonArray;
@JavaScript({"slider-connector.js", "jquery-ui.js"})
@StyleSheet({"jquery-ui.css"})
public class SliderServerComponent extends AbstractJavaScriptComponent {
public SliderServerComponent() {
addFunction("valueChanged", arguments -> {
JsonArray array = arguments.getArray(0);
double[] values = new double[2];
values[0] = array.getNumber(0);
values[1] = array.getNumber(1);
listener.valueChanged(values);
});
}
@Override
protected SliderState getState() {
return (SliderState) super.getState();
}
The server component defines getters and setters to work with the slider state and an interface of value
change listeners. The class extends AbstractJavaScriptComponent.
The addFunction() method invocation in the class constructor defines a handler for an RPC-call of
the valueChanged() function from the client.
The @JavaScript and @StyleSheet annotations point to files that must be loaded on the web page.
In our example, these are JavaScript files of the jquery-ui library, the connector and the stylesheet for
jquery-ui. You should place these files to the Java package of the Vaadin server component.
Download an archive with jQuery UI from http://jqueryui.com/download and put files jquery-ui.js and
jquery-ui.css from the archive to the Java package of the SliderServerComponent class. At the
jQuery UI download page, you can select which components should be put into the archive. For this demo, it
is enough to select only the Slider item of the Widgets group.
com_company_jscomponent_web_toolkit_ui_slider_SliderServerComponent = function() {
var connector = this;
var element = connector.getElement();
$(element).html("<div/>");
$(element).css("padding", "5px 10px");
connector.onStateChange = function() {
var state = connector.getState();
slider.slider("values", state.values);
slider.slider("option", "min", state.minValue);
slider.slider("option", "max", state.maxValue);
$(element).width(state.width);
}
}
Connector is a function that initializes a JavaScript component when the web page is loaded. The
function name must correspond to the server component class name where dots in package name are
replaced with underscore characters.
Vaadin adds several useful methods to the connector function. this.getElement() returns an HTML
DOM element of the component, this.getState() returns a state object.
Our connector does the following:
Initializes the slider component of the jQuery UI library. The slide() function is invoked when the
position of any drag handler changes. This function in turn invokes the valueChanged() connector
method. valuedChanged() is the method that we defined on the server side in the
SliderServerComponent class.
Defines the onStateChange() function. It is called when the state object is changed on the server
side.
To demonstrate how the component works, let’s create the Product entity with three attributes:
Generate standard screens for the entity. Ensure that the value of the In module field is Web Module.
The slider component will set minimal and maximum discount values of a product.
Open the product-edit.xml file. Make minDiscount and maxDiscount fields not editable by adding
the editable="false" attribute to the corresponding elements. Then add the new custom slider field
to the fieldGroup.
As a result, the XML descriptor of the editor screen should look as follows:
Open the ProductEit.java file. Replace its content with the following code:
package com.company.jscomponent.web.product;
import com.company.jscomponent.entity.Product;
import com.company.jscomponent.web.toolkit.ui.slider.SliderServerComponent;
import com.haulmont.cuba.gui.components.AbstractEditor;
import com.haulmont.cuba.gui.components.Component;
import com.haulmont.cuba.gui.components.FieldGroup;
import com.haulmont.cuba.gui.components.VBoxLayout;
import com.haulmont.cuba.gui.xml.layout.ComponentsFactory;
import com.haulmont.cuba.web.gui.components.WebComponentsHelper;
import com.vaadin.ui.Layout;
import javax.inject.Inject;
@Inject
private FieldGroup fieldGroup;
@Inject
private ComponentsFactory componentsFactory;
@Override
@Override
protected void postInit() {
super.postInit();
The initNewItem() method sets initial values for discounts of a new product.
Method init() initializes the slider custom field. It sets current, minimal and maximum values of the
slider and defines the value change listener. When the drag handler moves, a new value will be set to the
corresponding field of the editable entity.
Start the application server and open the product editor screen. Changing the drop handler position must
change the value of the text fields.
Click the Create web-toolkit module link in the Project properties navigator section.
Click the Create new UI component link. The New UI component page will open. Select the New GWT
component value in the Component type section.
Deselect the Integrate into Generic UI flag. The process of integration into the Generic UI is the same as
described at Integrating a Vaadin Component into the Generic UI, so we won’t repeat it here.
Let’s look at the generated files and make necessary changes in them.
RatingFieldWidget is a GWT widget. Replace its content with the following code:
package com.company.ratingsample.web.toolkit.ui.client.ratingfield;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.FocusWidget;
import java.util.ArrayList;
import java.util.List;
public RatingFieldWidget() {
DivElement container = DOM.createDiv().cast();
container.getStyle().setDisplay(Display.INLINE_BLOCK);
for (int i = 0; i < 5; i++) {
SpanElement star = DOM.createSpan().cast();
stars.add(star);
}
setElement(container);
setStylePrimaryName(CLASSNAME);
}
switch (event.getTypeInt()) {
// react on ONCLICK event
case Event.ONCLICK:
SpanElement element = event.getEventTarget().cast();
// if click was on the star
int index = stars.indexOf(element);
if (index >= 0) {
int value = index + 1;
// set internal value
setValue(value);
// notify listeners
if (listener != null) {
listener.starClicked(value);
}
}
break;
}
}
@Override
public void setStylePrimaryName(String style) {
super.setStylePrimaryName(style);
updateStarsStyle(this.value);
}
star.removeClassName(getStylePrimaryName() + "-star-selected");
}
A widget is a client-side class responsible for displaying the component in the web browser and handling
events. It defines interfaces for working with the server side. In our case these are the setValue()
method and the StarClickListener interface.
RatingFieldServerComponent is a Vaadin component class. It defines an API for the server code,
accessor methods, event listeners and data sources connection. Developers use the methods of this
class in the application code.
package com.company.ratingsample.web.toolkit.ui;
import com.company.ratingsample.web.toolkit.ui.client.ratingfield.RatingFieldServerRpc;
import com.company.ratingsample.web.toolkit.ui.client.ratingfield.RatingFieldState;
import com.vaadin.ui.AbstractField;
public RatingFieldServerComponent() {
// register an interface implementation that will be invoked on a request from
the client
registerRpc((RatingFieldServerRpc) value -> setValue(value, true));
}
@Override
protected RatingFieldState getState(boolean markAsDirty) {
return (RatingFieldState) super.getState(markAsDirty);
}
// we need to refresh the state when setValue is invoked from the application code
@Override
protected void setInternalValue(Integer newValue) {
super.setInternalValue(newValue);
if (newValue == null) {
newValue = 0;
}
getState().value = newValue;
}
}
The RatingFieldState state class defines what data are sent between the client and the server. It
contains public fields that are automatically serialized on server side and deserialized on the client.
package com.company.ratingsample.web.toolkit.ui.client.ratingfield;
import com.vaadin.shared.AbstractFieldState;
The RatingFieldServerRpc interface defines a server API that is used from the client-side. Its
methods may be invoked by the RPC mechanism built into Vaadin. We will implement this interface in
the component.
package com.company.ratingsample.web.toolkit.ui.client.ratingfield;
import com.vaadin.shared.communication.ServerRpc;
import com.company.ratingsample.web.toolkit.ui.RatingFieldServerComponent;
import com.company.ratingsample.web.toolkit.ui.client.ratingfield.RatingFieldServerRpc;
import com.company.ratingsample.web.toolkit.ui.client.ratingfield.RatingFieldState;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractFieldConnector;
import com.vaadin.shared.ui.Connect;
if (widget.listener == null) {
widget.listener = new RatingFieldWidget.StarClickListener() {
@Override
public void starClicked(int value) {
getRpcProxy(RatingFieldServerRpc.class).starClicked(value);
}
};
}
return widget;
}
The RatingFieldWidget class does not define the component appearance, it only assigns style names
to key elements. To define an appearance of the component, we’ll create stylesheet files. Click the Create
theme extension link on the Project properties navigator section. Select the halo theme in the dialog.
Studio creates SCSS files for theme extension in the themes directory of the web module. The halo theme
uses FonAwesome font glyphs instead of icons. We’ll use this fact.
&:after {
content: '\f006'; // 'fa-star-o'
}
}
.#{$primary-stylename}-star-selected {
&:after {
content: '\f005'; // 'fa-star'
}
}
.#{$primary-stylename} .#{$primary-stylename}-star:last-child {
padding-right: 0;
}
.#{$primary-stylename}.v-disabled .#{$primary-stylename}-star {
cursor: default;
}
}
@import "components/ratingfield/ratingfield";
@include ratingfield;
}
To demonstrate how the component works let’s create a new screen in the web module.
Add the screen to the application menu. Go to the Main menu navigator section and click the Edit button.
The menu editor will open. Add the screen created earlier to the application menu.
Open the rating-screen.xml file in the IDE. We need a container for our component. Declare it in the
screen XML:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
caption="msg://caption"
class="com.company.ratingsample.web.screens.RatingScreen"
messagesPack="com.company.ratingsample.web.screens">
<layout expand="container">
<vbox id="container">
<!-- we'll add vaadin component here-->
</vbox>
</layout>
</window>
Open the RatingScreen.java screen controller and add the code that puts the component to the screen.
package com.company.ratingsample.web.screens;
import com.company.ratingsample.web.toolkit.ui.RatingFieldServerComponent;
import com.haulmont.cuba.gui.components.AbstractWindow;
import com.haulmont.cuba.gui.components.BoxLayout;
import com.haulmont.cuba.web.gui.components.WebComponentsHelper;
import com.vaadin.ui.Layout;
import javax.inject.Inject;
import java.util.Map;
@Override
public void init(Map<String, Object> params) {
super.init(params);
com.vaadin.ui.Layout containerLayout = (Layout)
WebComponentsHelper.unwrap(container);
RatingFieldServerComponent field = new RatingFieldServerComponent();
field.setCaption("Rate this!");
containerLayout.addComponent(field);
}
}
This section describes how to integrate a new custom visual component into CUBA Studio. As a result of
the integration, the component will be available on the component palette of the WYSIWYG screen layout
designer. Developers will be able to drag and drop the component to the canvas and edit its properties in
the properties panel.
Let’s walk through the process of integrating the stepper component into Studio. Creation of this
component was described in Integrating a Vaadin Component into the Generic UI.
If you didn’t create this project, you can still reproduce the steps listed below in a new project. In this
case, you will see how Studio supports the component, but you won’t be able to run the application.
Click the Extend Studio link on the Project properties navigator section.
Component namespace URI - a namespace from the XSD that describes the Generic UI component. If
you’ve generated the new component with Studio, then you can take the value of this field from the
ui-component.xsd file located in the gui module.
Component namespace prefix - a prefix for the component XML element.
Standard properties - standard properties that should be available for editing in the component
properties panel of the Studio screen layout designer.
Select caption, datasource and property checkboxes.
id, align, height, width, enable, stylename and visible properties are available to any
component by default.
Custom properties - this table is used for declaring component specific properties that should be edited
in the component properties panel.
Add the following properties:
manualInput of type Boolean, the default value is true
mouseWheel of type Boolean, the default value is true
stepAmount, of type Integer, the default value is 0
maxValue, of type Integer, the default value is 0
minValue, of type Integer, the default value is 0
The custom visual components support is initialized when the Studio server start. Go to the Studio server
window, stop the server and start it again.
Re-create standard screens for the Customer entity to erase the results of our previous experiments.
Then go to the Screens navigator section and open the customer-edit.xml screen.
Remove the score field from fieldGroup because we will use a separate component for editing the
score.
Find the new Stepper component on the components palette, then drag it to the screen below
fieldGroup.
id - stepper
caption - Stepper
datasource - customerDs
property - score
maxValue - 50
messagesPack="com.company.addonguidemo.gui.customer"
xmlns:app="http://schemas.company.com/agd/0.1/ui-component.xsd">
<dsContext>
<datasource id="customerDs"
class="com.company.addonguidemo.entity.Customer"
view="_local"/>
</dsContext>
<layout expand="windowActions"
spacing="true">
<fieldGroup id="fieldGroup"
datasource="customerDs">
<column width="250px">
<field id="name"/>
</column>
</fieldGroup>
<app:stepper id="stepper"
caption="Stepper"
datasource="customerDs"
maxValue="50"
property="score"/>
<frame id="windowActions"
screen="editWindowActions"/>
</layout>
</window>
There is a new namespace with the app prefix in the screen XML, the stepper component is added to the
screen, and its properties are set correctly.
In this section, we’ll consider an example of creating an application component and using it in a project. The
component will provide a "Customer Management" functionality and include the Customer entity and
corresponding UI screens. The application will use the Customer entity from the component as a reference
in its Order entity.
If your component contains @MappedSuperclass persistent classes, make sure they have
descendants which are entities (i.e. annotated with @Entity) in the same project. Otherwise
such base classes will not be properly enhanced and you will not be able to use them in
applications.
4. Generate DB scripts and create standard screens for the Customer entity:
cust$Customer.browse and cust$Customer.edit. After that, go to main menu designer and
rename the application menu item to customerManagement.
5. Click to the App component descriptor link on the Project properties panel. Save the generated
descriptor by clicking OK.
6. Test the Customer Management functionality: Run > Create database, Run > Start application
server, then open http://localhost:8080/cust in your web browser.
7. Install the application component into the local Maven repository by executing the Run > Install app
component menu command.
Using the Install app component command is necessary on Windows to avoid file locking
issues. On Linux or Mac OS, you can also use the install Gradle task directly.
drop-down list.
4. Generate DB scripts and create standard screens for the Order entity. When creating standard
screens, create a order-with-customer-view view that includes the customer attribute and use
it for the screens.
5. Test the application functionality: Run > Create database, Run > Start application server, then
open http://localhost:8080/app in your web browser. The application will contain two top
level menu items: Customer Management and Application with the corresponding functionality.
rootProject['repoUser'] : 'admin')
password(rootProject.hasProperty('repoPass') ?
rootProject['repoPass'] : 'admin123')
}
}
...
cuba {
...
uploadRepository {
url = 'http://repo.company.com/nexus/content/repositories/snapshots' //
repository for uploading your artifacts
user = 'admin'
password = 'admin123'
}
}
4. Open the command line and run gradle assemble in the customers project root directory. This
ensures your new repository caches CUBA artifacts required for working in Studio.
5. In the Studio server window, specify your repository and credentials instead of the standard CUBA
repository. Start the Studio server.
6. Open the customers project in Studio.
7. In the Studio Search dialog (Alt-/), find the uploadArchives Gradle task and run it. You can also
run this task from the command line. The Customer Management component artifacts will be
uploaded to your repository.
8. Remove the component artifacts from your local Maven repository to ensure that they will be
downloaded from the remote repository when the sales application is assembled the next time: just
delete the .m2/repository/com/company folder located in your user home directory.
9. Open the customers project in Studio. The repository URL in its build.gradle will be
automatically changed to the one specified in the Studio server window.
10. Now you can assemble and run the application - the Customer Management component will be
downloaded from the remote repository.
6. Application Deployment
This chapter describes different aspects of CUBA applications deployment and operation.
In the example above, the application prevents a single point of failure existence, provides load balancing
and various client types connection. In the simplest case, however, the server part of an application can be
installed on one computer that includes the database. Various deployment options depending on load and
fault tolerance requirements are described in detail in Application Scaling.
local.app.properties – a file that defines deployment parameters of the server-based application blocks.
metadata.xml, persistence.xml, views.xml, remoting-spring.xml configuration files.
XML-descriptors of UI screens.
Controllers of UI screens in the form of Java or Groovy source code.
Groovy scripts or classes, and Java source code that is used by the application via the Scripting
interface.
The location of the configuration directory is determined by the cuba.confDir application property. For the
Middleware, Web Client and Web Portal blocks in fast deployment configuration in Tomcat, it is a
subdirectory with the web application name in the tomcat/conf directory, for example, tomcat/conf
/app-core for the Middleware.
For example, the filestorage subdirectory of the work directory is used by the file storage. In addition,
the Middleware block saves generated persistence.xml and orm.xml files in the work directory on start.
Work directory location is determined by the cuba.dataDir application property. For the Middleware, Web
Client and Web Portal blocks in fast deployment configuration in Tomcat, it is a subdirectory with the name
of the web application in the tomcat/work directory.
This directory can also be used to store arbitrary information about the running application. The log directory
location is determined by cuba.logDir application property. For the Middleware, Web Client and Web Portal
blocks in fast deployment configuration in Tomcat, it is the tomcat/logs directory.
The script directory structure reproduces the one described in Scripts to Create and Update the Database,
but it also has an additional top level that separates application components and the application scripts. The
numbering of top level directories is performed by project build tasks.
The DB scripts directory location is determined by cuba.dbDir application property. For fast deployment
configuration in Tomcat, it is the WEB-INF/db subdirectory of the middleware web application directory:
tomcat/webapps/app-core/WEB-INF/db.
Fast deployment is performed using the deploy task that is declared for core and web modules in the
build.gradle file. Before the first execution of deploy, a local Tomcat server should be set up and
initialized using the setupTomcat task.
As result of fast deployment, the following structure is created in the directory that is specified by the
cuba.tomcat.dir property of the build.gradle script (only important directories and files are listed
below):
bin/
setenv.bat, setenv.sh
startup.bat, startup.sh
debug.bat, debug.sh
shutdown.bat, shutdown.sh
conf/
catalina.properties
server.xml
logback.xml
logging.properties
Catalina/
localhost/
app/
app-core/
lib/
hsqldb-2.2.9.jar
logs/
app.log
shared/
lib/
temp/
app/
app-core/
webapps/
app/
app-core/
work/
app/
app-core/
bin – the directory that contains tools to start and stop the Tomcat server:
setenv.bat, setenv.sh – the scripts that set environment variables. These scripts should be used
for setting JVM memory parameters, specifying a configuration file for logging, configuring access to
JMX, parameters to connect the debugger.
startup.bat, startup.sh – the scripts that start Tomcat. The server starts in a separate console
window on Windows and in background on *nix.
To start the server in the current console window, use the following commands instead of
startup.*:
> catalina.bat run
$ ./catalina.sh run
debug.bat, debug.sh – the scripts that are similar to startup.*, but start Tomcat with an ability to
connect the debugger. These scripts are launched when running the start task of the build script.
shutdown.bat, shutdown.sh – the scripts that stop Tomcat.
conf – the directory that contains configuration files of Tomcat and its deployed applications.
catalina.properties – the Tomcat properties. To load shared libraries from the shared/lib
directory (see below), this file should contain the following line:
shared.loader=${catalina.home}/shared/lib/*.jar
The deploy task of the build script copies all libraries not listed in the jarNames parameter, i.e. not
specific for the given application, into this directory.
temp/app, temp/app-core – web client and the middleware applications temporary directories.
webapps – web application directories. Each application is located in its own subdirectory in the
exploded WAR format.
The deploy task of the build script create application subdirectories with the names specified in the
appName parameters and, among other things, copy the libraries listed in the jarNames parameter to
the WEB-INF/lib subdirectory for each application.
work/app, work/app-core – web client and the middleware applications work directories.
You can use this Tomcat instance in production just by copying the tomcat directory to the server. All you
have to do is to set up the server host name in both conf/app/local.app.properties and
conf/app-core/local.app.properties files (create the files if they do not exist):
cuba.webHostName = myserver
cuba.webAppUrl = http://myserver:8080/app
Besides, set up the connection to you production database. You can do it in the context.xml file of your web
application (webapps/app-core/META-INF/context.xml), or copy this file to conf/Catalina
/localhost/app-core.xml as described in the previous section to separate development and
production settings.
You can create the production database from a development database backup, or set up the automatic
creation and further updating of the database. See Creating and Updating the Database in Production.
If you want to change the Tomcat port or web context (the last part of the URL after /), use Studio:
If you want to use the root context for the web client (http://myserver:8080), rename app directories to
ROOT
tomcat/
conf/
ROOT/
local.app.properties
app-core/
local.app.properties
webapps/
ROOT/
app-core/
Please note that we build two separate WAR files for Middleware and Web Client blocks here. If
you want to combine them into one WAR file with the singleWar = true parameter, provide the
special web.xml file as described in this section.
As a result, the app-core.war and app.war files will be created in the build\distributions\war
project subdirectory.
3. Create an application home directory, for example, c:\work\app_home.
4. Download and install Jetty to a local directory, for example c:\work\jetty-home. This example has
been tested on jetty-distribution-9.3.6.v20151106.zip.
5. Create the c:\work\jetty-base directory, open the command prompt in it and execute:
java -jar c:\work\jetty-home\start.jar --add-
to-start=http,jndi,deploy,plus,ext,resources
6. Create the c:\work\jetty-base\app-jetty.xml file with the following contents (for a PostgreSQL
database named test):
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<New id="CubaDS" class="org.eclipse.jetty.plus.jndi.Resource">
<Arg></Arg>
<Arg>jdbc/CubaDS</Arg>
<Arg>
<New class="org.postgresql.ds.PGSimpleDataSource">
<Set name="ServerName">localhost</Set>
<Set name="PortNumber">5432</Set>
<Set name="DatabaseName">test</Set>
<Set name="User">cuba</Set>
<Set name="Password">cuba</Set>
</New>
</Arg>
</New>
</Configure>
8. Copy the JDBC driver for your database to the c:\work\jetty-base\lib\ext directory. You can
take the driver file from the CUBA Studio lib directory or from the build\tomcat\lib project
directory. In case of PostgreSQL database, it is postgresql-9.1-901.jdbc4.jar.
9. Copy WAR files to the c:\work\jetty-base\webapps directory.
10. Open the command prompt in the c:\work\jetty-base directory and run:
java -jar c:\work\jetty-home\start.jar
If the target server parameters differ from what you have on the local Tomcat used for fast deployment,
provide appropriate application properties. For example, if the target server runs on port 9999, the task
definition should be as follows:
task buildWar(type: CubaWarBuilding) {
appHome = './app_home'
singleWar = false
includeContextXml = true
includeJdbcDriver = true
appProperties = [
'cuba.automaticDatabaseUpdate': true,
'cuba.webPort': 9999,
'cuba.connectionUrlList': 'http://localhost:9999/app-core'
]
}
You can also specify a different context.xml file to setup the connection to the production database,
for example:
task buildWar(type: CubaWarBuilding) {
appHome = './app_home'
singleWar = false
includeContextXml = true
includeJdbcDriver = true
appProperties = ['cuba.automaticDatabaseUpdate': true]
coreContextXmlPath = 'modules/core/web/META-INF/production-context.xml'
}
2. Run the buildWar gradle task. As a result, app.war and app-core.war files will be generated in the
build/distibutions directory of your project.
gradlew buildWar
Please note that only projects using PostgreSQL or HSQL databases are currently supported.
1. Click the Deployment settings link on the Project properties section and switch to the CLOUD tab.
2. If the project is not yet set up for cloud deployment, you can use the field on top to create a free trial
Jelastic account.
3. After completing your registration, enter the email, password and selected provider.
4. Environment field defines the environment in which the application WAR will be deployed. Click on the
ellipsis button and select an existing environment or create a new one. You can check the selected
environment for compatibility with your project. A compatible environment should have Java 8, Tomcat 8
and PostgreSQL 9.1+ (if the project uses PostgreSQL database). If your project uses PostgreSQL, you
will receive an email with the database connection details. Please use them when generating custom
context.xml file, see Custom context.xml path field below. Besides, you should create an empty
PostgreSQL database using the provider’s web interface link containing in the email. The database
name should be specified later in custom context.xml (see below).
5. Press Generate button next to the Custom web.xml path field. Studio will generate a special web.xml
of the single WAR comprising the Middleware and Web Client application blocks.
6. If your project uses HSQLDB, that is all - you can press OK and start deployment by clicking Run >
Deploy to cloud main menu item.
7. If your project uses PostgreSQL, go to the database administration web interface by the link in the email
recieved after creation of the environment and create a database.
8. Press Generate button next to the Custom context.xml path field and specify the database user,
password, host and name.
9. Leave the Include JDBC driver and Include context.xml checkboxes selected.
10. Now you can press OK and start deployment by clicking Run > Deploy to cloud main menu item.
11. After completing the deployment, use the link at the bottom left corner to open the application web
interface.
CUBA Studio provides support of IBM® Bluemix® cloud deployment in a few easy steps.
Bluemix cloud deployment is currently applicable only for projects using PostgreSQL database.
HSQLDB is available with in-process option only, that means the database will be recreated on every
application restart, and the user data will be lost.
d. Click Generate button near the Custom context.XML field. In the opened dialog fill the credentials
of the Database you have created in Bluemix.
Use the credentials from uri of your DB service following the example below:
{
"elephantsql": [
{
"credentials": {
"uri": "postgres://ldwpelpl:eFwXx6lNFLheO5maP9iRbS77Sk1VGO_T@echo-
01.db.elephantsql.com:5432/ldwpelpl"
}
}
]
}
where
path is the relative path to WAR-file.
memory: the default memory limit is 1G. You may want to allocate less or more memory to your
application, this can also be done via Bluemix WEB interface. Note that the allocated memory affects
the Runtime Cost.
name is the name of the Tomcat application you have created in the Cloud above.
host: the same as name.
env: the environment variables used to set the Tomcat and Java versions.
12. In the command line switch to the root directory of your CUBA project.
cd your_project_directory
cf push
The push command gets all the required parameters from the manifest.yml file.
16. You can find Tomcat server logs via Bluemix WEB-interface in the Logs tab on the application
dashboard, as well as in command line using the command
cf logs cuba-app --recent
17. After the deployment process is completed, your application will become accessible in browser using
the URL host.domain. This URL will be displayed in the ROUTE field in the table of your Cloud
Foundry Apps.
Servers host1 and host2 host Tomcat instances with the app web-app implementing the Web Client
block. Users access the load balancer at http://host0/app, which redirects their requests to the
servers. Server host3 hosts a Tomcat instance with the app-core web-app that implements the
Middleware block.
worker.list=tomcat1,tomcat2,loadbalancer,jkstatus
worker.tomcat1.port=8009
worker.tomcat1.host=host1
worker.tomcat1.type=ajp13
worker.tomcat1.connection_pool_timeout=600
worker.tomcat1.lbfactor=1
worker.tomcat2.port=8009
worker.tomcat2.host=host2
worker.tomcat2.type=ajp13
worker.tomcat2.connection_pool_timeout=600
worker.tomcat2.lbfactor=1
worker.loadbalancer.type=lb
worker.loadbalancer.balance_workers=tomcat1,tomcat2
worker.jkstatus.type=status
</VirtualHost>
1. In tomcat/conf/server.xml, add the jvmRoute parameter equivalent to the name of the worker
specified in the load balancer settings for tomcat1 and tomcat2:
<Server port="8005" shutdown="SHUTDOWN">
...
<Service name="Catalina">
...
<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">
...
</Engine>
</Service>
</Server>
cuba.webHostName = host1
cuba.webPort = 8080
cuba.webContextName = app
Servers host1 and host2 host Tomcat instances with the app web-app implementing the Web Client
block. Cluster configuration for these servers is described in the previous section. Servers host3 and
host4 host Tomcat instances with the app-core web-app implementing the Middleware block. They are
configured to interact and share information about user sessions, locks, cash flushes, etc.
cuba.webHostName = host1
cuba.webPort = 8080
cuba.webContextName = app
The order of servers in cuba.connectionUrl defines priority and order for the client to send the requests.
In the example above, the client will first attempt to access host3, and then, if it is not available, host4. If a
request to host4 completes successfully, the client will save host4 as the first server in the list and will
continue working with this server. Restarting a client will reset the initial values. Uniform distribution of
clients among all servers can be achieved using the cuba.randomServerPriority property.
cuba.webHostName = host3
cuba.webPort = 8080
cuba.webContextName = app-core
For the Middleware servers, correct values of the cuba.webHostName, cuba.webPort and
cuba.webContextName properties should be specified to form a unique Server ID.
Interaction mechanism is based on JGroups. It is possible to fine-tune the interaction using the
jgroups.xml file located in the root of cuba-core-<version>.jar. It can be copied to tomcat/conf
/app-core and configured as needed.
ClusterManagerAPI bean provides the program interface for servers interaction in the Middleware
cluster. It can be used in the application – see JavaDocs and examples in the platform code.
6.3.3. Server ID
Server ID is used for reliable identification of servers in a Middleware cluster. The identifier is formatted as
host:port/context:
tezis.haulmont.com:80/app-core
192.168.44.55:8080/app-core
Server ID can be obtained using the ServerInfoAPI bean or via the ServerInfoMBean JMX interface.
Without extra configuration, the console shows all JMX objects registered in the JVM where the Web Client
block of the current user is running. Therefore, in the simplest case, when all application blocks are
deployed to one web container instance, the console has access to the JMX beans of all tiers as well as the
JMX objects of the JVM itself and the web container.
Names of the application beans have a prefix corresponding to the name of the web-app that contains them.
For example, the app-core.cuba:type=CachingFacade bean has been loaded by the app-core
web-app implementing the Middleware block, while the app.cuba:type=CachingFacade bean has been
loaded by the app web-app implementing the Web Client block.
JMX console can also work with the JMX objects of a remote JVM. This is useful when application blocks
are deployed over several instances of a web container, for example separate Web Client and Middleware.
To connect to a remote JVM, a previously created connection should be selected in the JMX Connection
field of the console, or a new connection can be created:
To get a connection, JMX host, port, login and password should be specified. There is also the Host name
field, which is populated automatically, if any CUBA-application block is detected at the specified address. In
this case, the value of this field is defined as the combination of cuba.webHostName and cuba.webPort
properties of this block, which enables identifying the server that contains it. If the connection is done to a
3rd party JMX interface, then the Host name field will have the "Unknown JMX interface" value. However it
can be changed arbitrarily.
In order to allow a remote JVM connection, the JVM should be configured properly (see below).
Here, the java.rmi.server.hostname parameter should contain the actual IP address or the DNS
name of the computer where the server is running; com.sun.management.jmxremote.port sets the
port for JMX tools connection.
Edit the conf/jmxremote.access file. It should contain user names that will be connecting to the JMX
and their access level. For example:
admin readwrite
Edit the conf/jmxremote.password file. It should contain passwords for the JMX users, for example:
admin admin
The password file should have reading permissions only for the user running the Tomcat. server. You
can configure permissions the following way:
Open the command line and go to the conf folder
Run the command:`cacls jmxremote.password /P "domain_name\user_name":R`
where domain_name\user_name is the user’s domain and name
After this command is executed, the file will be displayed as locked (with a lock icon) in Explorer.
If Tomcat is installed as a Windows service, than the service should be started on behalf of the user who
has access permissions for jmxremote.password. It should be kept in mind that in this case the
bin/setenv.bat file is ignored and the corresponding JVM startup properties should be specified in
the application that configures the service.
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.password.file=../conf
/jmxremote.password -Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access"
Here, the java.rmi.server.hostname parameter should contain the real IP address or the DNS
name of the computer where the server is running; com.sun.management.jmxremote.port sets the
port for JMX tools connection
Edit conf/jmxremote.access file. It should contain user names that will be connecting to the JMX
and their access level. For example:
admin readwrite
Edit the conf/jmxremote.password file. It should contain passwords for the JMX users, for example:
admin admin
The password file should have reading permissions only for the user running the Tomcat server.
Permissions for the current user can be configured the following way:
Open the command line and go to the conf folder.
Run the command:
chmod go-rwx jmxremote.password
By default, server push uses the WebSocket protocol. The following application properties affect the
platform server push functionality:
cuba.web.pushLongPolling
cuba.web.pushEnabled
If users connect to the application server via a proxy that does not support WebSocket, set
cuba.web.pushLongPolling to true and increase proxy request timeout to 10 minutes or more.
Below is an example of the Nginx web server settings for using WebSocket:
location / {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600;
proxy_connect_timeout 240;
proxy_set_header Host $host;
proxy_set_header X-RealIP $remote_addr;
proxy_pass http://127.0.0.1:8080/;
proxy_set_header X-Forwarded-Proto $scheme;
This section provides practical advice on working with databases during application development and in
production.
For information on configuration parameters for working with particular DBMS, see the Database
Components section.
The task to create and maintain the DB schema consists of two parts: creating the scripts and executing
them.
Scripts can be created both manually and using Studio. The process of creating scripts in Studio is provided
below. Run the Generate DB scripts command in the Entities section. In this case, Studio will connect to
the database defined on the Project properties page and compare the available DB schema with the
current data model.
If the database does not exist or does not have SYS_DB_CHANGELOG and SEC_USER tables, the
system generates only DB initialization scripts. Otherwise, update scripts are created as well. Then, a page
with the generated scripts is opened.
Update scripts are displayed on the Update scripts tab. Scripts with the new status reflect the difference
between the current state of the data model and the DB schema. A separate script is created for each new
or modified table. Some scripts also contain sets of referential integrity constraints. When the page is closed
by clicking OK, the scripts are saved in the db/update/{db_type} directory of the core module.
Scripts that exist in the project and have been applied to the DB before are displayed with the applied
status. They cannot be edited or removed.
The Update scripts tab can also display scripts with to be deleted status. These are the scripts available in
the project, but not applied to the DB yet. These scripts are removed when you close the page by clicking
OK. This is the standard behavior in case the scripts are created during previous scripts generation, but not
applied by invoking Update database. In this case, you don’t need them any longer, because the current
difference between the DB schema and the data model is reflected in newly generated scripts. However, if
the scripts were authored by another developer and retrieved from a version control system, you should
cancel the saving and apply the other party’s scripts to your DB first, and then generate new ones.
The Init tables, Init constraints and Init data tabs display DB create scripts that are located in the
db/init/{db_type} directory of the core module.
The Init tables tab displays the 10.create-db.sql script that creates the tables. The code related to one
and the same table is separated by begin {table_name} ... end {table_name} comments. When
an entity in the model is changed, Studio will replace code only for the corresponding table between the
comments, while leaving the rest of the code, where manual changes could have been made, untouched.
Therefore, do not remove these comments when editing the code manually, otherwise Studio will not be
able to properly apply the changes to the existing files.
The Init constraints tab displays the 20.create-db.sql script that creates integrity constraints. It also
has table-separating comments that you should not remove.
The Init data tab displays the 30.create-db.sql script designed to provide additional information when
initializing the DB. These may be, for example, functions, triggers or DML operators to fill the database with
the necessary data. The contents of this script are created manually, if necessary.
At the initial stage of application development, when the data model is being actively changed, we
recommend using only the DB creation scripts (located in the Init tables, Init constraints, Init data)
tabs) and removing the update scripts in the Update scripts tab immediately after invoking the
Generate DB scripts command. This is the most simple and reliable way to keep the DB up to date.
Of course, it has a major drawback, since applying these scripts recreates the DB from scratch, and all
data are lost. You can partially compensate this drawback at the development stage by adding
commands to the Init data script that will create primary data upon initialization.
Update scripts become a convenient and necessary tool for developing and maintaining the DB at a
later stage, when the data model is relatively stable, and the development and production databases
have the data that cannot not be lost as a result of recreating the DB from scratch.
Use DB script execution by Gradle tasks to apply scripts: invoke Run > Create database to recreate the
database and Run > Update database to apply the scripts. Please note that these items are available only
if the application server is stopped. Of course, you can invoke the corresponding Gradle tasks (createDb
and updateDb) at any time from the command line, but if the database or any of its objects are locked,
script execution may fail.
By default, a cluster index is based on the table’s primary key, however keys of the UUID type used by
CUBA applications are poorly suited for clustered index. We recommend creating UUID primary keys with
the nonclustered modificator:
create table SALES_CUSTOMER (
ID uniqueidentifier not null,
CREATE_TS datetime,
...
primary key nonclustered (ID)
)^
MySQL does not support partial indexes, so the only way to implement a unique constraint for a soft deleted
entity is to use the DELETE_TS column in the index. But there is another problem: MySQL allows multiple
NULLs in a column with a unique constraint. Since the standard DELETE_TS column is nullable, it cannot be
used in the unique index. We recommend the following workaround for creating unique constraints for soft
deleted entities:
1. Create a DELETE_TS_NN column in the database table. This column is not null and is initialized by a
default value:
create table DEMO_CUSTOMER (
...
DELETE_TS_NN datetime(3) not null default '1000-01-01 00:00:00.000',
...
)
2. Create a trigger that will change DELETE_TS_NN value when DELETE_TS value is changed:
create trigger DEMO_CUSTOMER_DELETE_TS_NN_TRIGGER before update on DEMO_CUSTOMER
for each row
if not(NEW.DELETE_TS <=> OLD.DELETE_TS) then
set NEW.DELETE_TS_NN = if (NEW.DELETE_TS is null, '1000-01-01 00:00:00.000',
NEW.DELETE_TS);
end if
PostgreSQL
In order to use non-default schema on PostgreSQL, specify the currentSchema parameter in the
connectionParams property of the createDb and updateDb Gradle tasks, for example:
task createDb(dependsOn: assembleDbScripts, type: CubaDbCreation) {
dbms = 'postgres'
host = 'localhost'
dbName = 'my_db'
connectionParams = '?currentSchema=my_schema'
dbUser = 'cuba'
dbPassword = 'cuba'
}
If you are using Studio, add this connection parameter to the Connection params field on the Project
properties page. Studio will update build.gradle automatically. After that, you can update or re-create
the database, and all tables will be created in the specified schema.
On Microsoft SQL Server, providing a connection property is not enough, you have to link the schema with
the database user. Below is an example of creating a new database and using a non-default schema in it.
Create a login:
create login JohnDoe with password='saPass1'
Connect to the new database as sa, create a schema, then create a user and give him owner rights:
create schema my_schema
Now you should specify the currentSchema parameter in the connectionParams property of the
updateDb Gradle task (or in Studio project properties). In fact, this property is not handled by SQL Server
JDBC driver, but it tells Studio and CUBA Gradle plugin what schema to use.
task updateDb(dependsOn: assembleDbScripts, type: CubaDbUpdate) {
dbms = 'mssql'
dbmsVersion = '2012'
host = 'localhost'
dbName = 'my_db'
connectionParams = ';currentSchema=my_schema'
dbUser = 'JohnDoe'
dbPassword = 'saPass1'
}
Keep in mind, that you cannot re-create the SQL Server database from Studio or by executing createDb in
the command line, because non-default schema requires association with a user. But if you run Update
database in Studio or updateDb in the command line, all required tables will be created in the existing
database and specified schema.
Enable the cuba.automaticDatabaseUpdate application property by adding the following line to the
local.app.properties file of the Middleware block:
cuba.automaticDatabaseUpdate = true
For fast deployment to Tomcat, this file is located in the tomcat/conf/app-core directory. If the file
does not exist, create it.
Create an empty database corresponding to the URL specified in the data source description in
context.xml.
Start the application server containing the Middleware block. At application start, the database will be
initialized and ready for work.
After that, each time when the application server starts, a scripts execution mechanism will compare the set
of scripts located in the database scripts directory with the list of already executed scripts registered in the
database. If new scripts are found, they will be executed and registered as well. Typically it is enough to
include the update scripts in each new application version, and the database will be actualized each time
when the application server is restarted.
When using the database scripts execution mechanism at server start, the following should be considered:
If any error occurs when running a script, the Middleware block stops initialization and becomes
inoperable. The client blocks generate messages about inability to connect to the Middleware.
Check the app.log file located in the server’s log folder for a message about SQL execution from the
com.haulmont.cuba.core.sys.DbUpdaterEngine logger and, possibly, further error messages to
identify the error reasons.
The update scripts, as well as the DDL and the SQL commands within the scripts separated with "^",
are executed in separate transactions. That is why when an update fails there is still a big chance that a
part of the scripts or even individual commands of the last script will have been executed and committed
to the database.
With this in mind, creating a backup copy of the database immediately before starting the server is highly
recommended. Then, when the error reason is fixed, the database can be restored and automatic
process restarted.
If the backup is missing, you should identify which part of the script was executed and committed after
the error is fixed. If the entire script failed to execute, the automatic process can be simply restarted. If
some of the commands before the erroneous one were separated with the "^" character, executed in a
separate transaction and committed, then the remaining part of the commands should be run and this
script should be registered in SYS_DB_CHANGELOG manually. After that, the server can be started
and the automatic update mechanism will start processing the next unexecuted script.
CUBA Studio generates update scripts with ";" delimiter for all database types except Oracle. If update
script commands are separated by semicolons, the script is executed in one transaction and entirely
rolled back in case of failure. This behavior ensures consistency between the database schema and the
list of executed update scripts.
dbUrl – database connection URL. For primary initialization, the specified database should be empty;
the database is not cleared automatically in advance.
scriptsDir – absolute path to the folder containing scripts in the standard structure. Typically, this is
the database scripts directory supplied with the application.
one of the possible commands:
create – initialize the database.
check – show all unexecuted update scripts.
update – update the database.
DB_URL="jdbc:postgresql://localhost/mydb"
APP_CORE_DIR="./../webapps/app-core"
WEBLIB="$APP_CORE_DIR/WEB-INF/lib"
SCRIPTS="$APP_CORE_DIR/WEB-INF/db"
TOMCAT="./../lib"
SHARED="./../shared/lib"
CLASSPATH=""
for jar in `ls "$TOMCAT/"`
do
CLASSPATH="$TOMCAT/$jar:$CLASSPATH"
done
This script is designed to work with the database named mydb running on the local PostgreSQL server. The
script should be located in the bin folder of the Tomcat server and should be started with {username},
{password} and {command}, for example:
./dbupdate.sh cuba cuba123 update
Script execution progress is displayed in the console. If any error occurs, same actions as described in the
When updating the database from the command line, the existing Groovy scripts are started, but only
their main part gets executed. Due to the lack of the server context, the script’s PostUpdate part is
ignored with the corresponding message written to the console.
Before starting Squirrel SQL, find the hsqldb-x.x.x.jar file in the lib folder of your CUBA Studio
installation and copy it to the lib folder of Squirrel SQL.
Start Squirrel SQL and go to Drivers. Make sure that HSQLDB Server driver is active.
Open Aliases tab and click the Create a new Alias button.
Fill in the connection properties in the window that appears: Database URL, User Name and Password. The
default user name is "sa", the password is empty. The database URL can be copied from the Project
properties tab in CUBA Studio, or from the modules/core/web/META-INF/context.xml file of the
application project.
If you cannot find the panel, open View → Tool Windows → Database.
In the appeared window select the hsqldb-x.x.x.jar driver that you can copy from the lib folder of
your CUBA Studio installation.
Then you should configure data source properties: database URL, User name and Password. Database
URL can be copied from the Project properties properties tab in CUBA Studio or from the
modules/core/web/META-INF/context.xml file of the application project. The default user name is
"sa", the password is empty.
If you are using PostgreSQL as the DBMS and uuid as the identifier, editing data with IntelliJ IDEA
may result in an error: ERROR: operator does not exist: uuid = character varying
To solve this problem, go to the Advanced tab in the data source settings and set stringtype to
unspecified.
8. Security Subsystem
The CUBA platform uses the following methods to control access rights:
The role-based system for assigning user permissions. A set of roles and permissions can be configured
by the system administrator during the system deployment or later in production.
A hierarchical structure of access groups with constraint inheritance.
Access control at the following levels:
Operations on entities (read, create, update, delete): for example, user Smith can view documents,
but cannot create, update or delete them.
Entity attributes (modify, read, access denied): user Smith can view all document attributes except
for amount.
Access to particular entity instances (access control at the row level): user Smith can view the
documents that have been created in their department only.
Integration with LDAP with an ability to implement SSO (Single Sign-On) for Windows users.
Security management screens – screens available to system administrator for configuring user access
rights.
Login screen − system login window. This window provides user authentication by username and password.
The database stores password hashes for security.
The UserSession object is created upon login. This is the central security element associated with the
currently authenticated user and containing information on data access rights.
Roles − user roles. A role is a system object, which, on the one hand, matches the permission set required
to perform specific functions, and on the other hand, the subset of users who must have these permissions.
Access Groups − user access groups. The groups have a hierarchical structure, with each element defining
a set of constraints, allowing controlling access to individual entity instances (at table row level). For
example, users can view the documents that have been created in their department only.
The Web Client’s Remember Me checkbox can be configured by using the cuba.web.rememberMeEnabled
application property. The drop-down list of supported languages on the standard login screen can be
configured with the cuba.localeSelectVisible and cuba.availableLocales application properties.
In Web Client, the standard login window can be customized or completely replaced in the project using the
Screens > Create login window link in Studio. See also Web Client Specifics.
The platform has a mechanism for the protection against password brute force cracking. It is described in
the Login section.
8.1.2. Users
Each system user has a corresponding instance of sec$User entity, containing unique login, password
hash, reference to access group and list of roles, and other attributes. User management is carried out
using the Administration > Users screen:
In addition to the standard actions to create, update and delete records, the following actions are available:
Copy – quickly creates a new user based on the selected one. The new user will have the same access
group and role set. Both can be changed in the new user edit screen.
Copy settings – enables copying user interface settings from one user to several others. The settings
include the table presentations, the SplitPanel separator position, filters and search folders.
Change password – allowing changing password for a selected user.
Reset passwords – enables performing the following actions on selected users:
If Generate new passwords flag is not selected in the Reset passwords for selected users dialog,
the Change password at next logon flag will be assigned to selected users. These users will be
asked to change the password on the next successful login.
If Generate new passwords flag is selected, new random passwords will be generated for selected
users and displayed to the system administrator. The list of passwords can be exported to XLS and
sent to related users. Additionally, the Change password at next logon flag will be set for each user,
ensuring that users change the password on next login.
If Send emails with generated passwords flag is selected in addition to Generate new passwords,
the automatically generated one-time passwords will be sent to corresponding users directly, and not
shown to system administrator.
If the user has substituted users, a drop-down list will be shown in the application upper right corner instead
of the plain text with the current user name:
If another user is selected in this list, all opened screens will be closed and the substitution will be made
active. The UserSession.getUser() method will still return the user that has logged in, however, the
UserSession.getSubstitutedUser() method will return the substituted user. If there is no
substitution, the UserSession.getSubstitutedUser() method will return null.
Substituted users can be managed through the Substituted Users table in the user edit screen. The user
substitution screen is described below:
User – the edited user. This user will substitute another user.
Substituted user – the substituted user.
Start date, End date – optional substitution period. User substitution will be unavailable outside of this
period. If no period is specified, substitution will be available until this table entry is removed.
A user can view and edit timestamp values in a time zone different from server’s time zone. There are two
ways to manage user’s time zone:
Time zone name dropdown allows a user to select the time zone explicitly.
Auto checkbox indicates that the time zone will be obtained from the current environment (web browser
for the web client or OS for the desktop client).
If both fields are empty, no time zone conversions are performed for the user. Otherwise, the platform saves
time zone in the UserSession object when user logs in and uses it for displaying and entering timestamp
values. The application code can also use the value returned by UserSession.getTimeZone() for
custom functionality.
If a time zone is in use for the current session, its short name and offset from GMT are displayed in the
application main window next to the current user’s name.
Time zone conversions are performed only for DateTimeDatatype entity attributes, i.e., timestamps.
Attributes storing date (DateDatatype) and time (TimeDatatype) separately are not affected. You
can also deny conversions for a timestamp attribute by annotating it with @IgnoreUserTimeZone.
8.1.3. Permissions
The permission determines the user’s right to any system object or functionality, such as screen, entity
operation, etc. The permission can either grant the user the right to the object, or revoke it (in essence, it is
actually a prohibition).
By default, the user has the right to an object, unless explicitly denied by a permission.
The permissions are granted by the sec$Permission entity instances and contain the following attributes:
type – permission type: determines the object type the permission is imposed on.
target – permission object: determines the specific object the permission is imposed on. The format of
the attribute depends on the permission type.
value – permission value. The value range depends on the permission type.
component path. The format of the component path is described in the next section.
8.1.4. Roles
The role combines a set of permissions that can be granted to the user.
The user may have several roles, in which case a logical sum (OR) is devised from all of the assigned roles.
For example, if a user has roles A, B and C, role A denies X, role B allows X, role C does not set explicit
permissions on X, then X will be allowed.
If no user roles explicitly define permission on the object, the user will have the permission for this object.
Therefore, the users have rights to all the objects if they have no roles that explicitly define the permission,
or have at least one role that grants the permission.
If a user has a single role without explicitly set permissions, or does not have any roles at all, he will
have all rights to all objects.
The role list is displayed in the Administration > Roles screen. In addition to the standard actions to
create, update, and delete records, the screen has the Assign to users button, allowing assigning the
selected role to multiple users.
The role edit screen is described below. The role attributes are displayed in the upper part:
Name – unique role name or id (required). The name cannot be changed after the role has been
created.
Localized name – user-friendly role name.
Description – arbitrary role description.
Type – role type, can be:
Standard – the role of this type grants only explicitly set permissions.
Super – the role of this type automatically grants all permissions. It should be assigned to system
administrators, since it removes all prohibitions set by other roles.
Read-only – the role of this type automatically denies the permissions for the following entity
operations: CREATE, UPDATE, DELETE. Therefore, the user with this role can only read the data
and is unable to update it (unless there are other user roles explicitly allowing these operations).
Denying – the role of this type automatically denies the permissions for all objects, except entity
attributes. In order to view or update something in the system, the user should be assigned an
additional role that explicitly gives the necessary rights.
Permissions can be explicitly set for all the role types; for example, you can add the permissions to
modify entities for the Read-only role. However, it does not make sense to prohibit anything for the
Super role, because this special role type removes all prohibitions.
A user with the Denying role cannot login to web or desktop client, because this type of role
also revokes the cuba.gui.loginToClient specific permission (displayed as "Login to
client" in the list of specific permissions). Therefore you have to grant this permission to users
explicitly - either in another role, or right in the denying role.
Default role – default role flag. All roles with this flag are automatically assigned to the newly created
users.
The tree in the left part of the tab reflects the structure of the application’s main menu. The last tree
element is Other screens, which contains screens without a main menu item (for example, entity edit
screens).
The Entities tab – configures entity operation permissions:
The Assigned only is selected by default, so that the table contains only the entities that have explicit
permissions in this role. Therefore, the table for a new role will be empty. In order to add permissions,
uncheck Assigned only and click Apply. The entity list can be filtered by entering a part of an entity
name in the Entity field and clicking Apply.
System level checkbox enables viewing and selecting system entities marked with the @SystemLevel
annotation, which are not shown by default.
The Attributes tab – configures entity attribute permissions:
The Permissions column in the entity table shows the list of the attributes that have explicit permissions.
The modify (full access) permissions are marked with green, read-only (read-only) – with blue, hide
(the attribute is hidden) – with red.
Entity list can be managed similarly to the list in the Entities tab.
The Specific tab configures named functionality permissions:
The permissions.xml project configuration file defines the object names to which specific permissions
can be assigned.
The UI tab configures UI screen component permissions:
The permissions on this screen allow restricting access to any screen component, including the ones not
associated with any data (for example, a container). The component identifiers must be known to create
such permissions, therefore access to the screen source code is required.
In order to create a constraint, select the desired screen in the Screen drop-down list, specify the
component path in the Component field, and click Add. Then set the access mode for the selected
component in the Permissions panel.
The rules to forming the component path are listed below:
If the component belongs to the screen, simply specify the component identifier, id.
If the component belongs to the frame that is embedded within the screen, specify the frame identifier,
and then the component identifier separated with period.
If configuring permission for the TabSheet tab or the FieldGroup field, specify the component
identifier, and then the tab or field identifier in square brackets.
To configure permission for an action, specify the component, holding the action, and then the action
identifier in angle brackets. For example: customersTable<changeGrade>.
The user can be added to one group only, however the list of constraints and session attributes from all the
groups up the hierarchy will be inherited.
User access groups can be managed from the Administration > Access Groups screen:
8.1.5.1. Constraints
Constraints restrict user access to entity instances. Unlike the permissions which are applied to classes of
entities, constraints are applied to particular entity instances that do not match the contraint conditions.
Constraints can be set for creation, reading, updating and deletion. Besides, one can add custom
constraints not related to CRUD actions.
A user gets the constraints list from all groups starting with their own one, and up the hierarchy. Thus,
the following principle is implemented: the lower the users are in the groups hierarchy, the more
constraints they have.
Note that constraints are checked for all operations performed from the client tier through the standard
DataManager. If an entity does not match the constraints conditions during creation, modification or deletion,
the RowLevelSecurityException is thrown.
There are three types of constraint check: check in database, check in memory, check in database and in
memory.
1. For the constraints with check in database, conditions are specified using JPQL expression fragments.
These fragments are appended to all entity instance selection queries. So the entities not matching the
conditions are filtered on the database level. Constraints with database check can be used only for the
read operation.
2. For the constraints with check in memory, the conditions are specified using Groovy expressions. The
expressions are executed for every entity in the checked graph of objects, and if the entity does not
match the conditions, it is filtered from the graph.
3. The constraints with check in database and in memory are combination of previous two types.
In order to create a constraint, open the Access Groups screen, select a group to create the constraint for,
go to the Constraints tab and click Create:
Select an entity from the Entity Name drop-down list, operation type from the Operation Type drop-down
list, check type from the Check type drop-down list. Depending on selected check type, you have to set
JPQL conditions in the Join Clause and Where Clause fields and/or Groovy condition in the Groovy Script
field. You can use the Constraint Wizard, which enables visual creation of the JPQL and Groovy
conditions. When you select custom operation type, the required Code field appears, and you should set a
specific code, which will be used to identify the constraint.
The JPQL editor in the Join Clause and Where Clause fields supports autocompletion for entity
names and their attributes. In order to invoke autocompletion, press Ctrl+Space. If the invocation is
made after the period symbol, an entity attributes list matching the context will be shown, otherwise –
a list of all data model entities.
The {E} string should be used as an alias of the entity being extracted. On execution of the query, it will
be replaced with a real alias, specified in the query.
The following predefined constants can be used in JPQL parameters:
session$userLogin – login name of the current user (in case of substitution – the login name of
the substituted user).
session$userId – ID of the current user (in case of substitution – ID of the substituted user).
session$userGroupId – group ID of the current user (in case of substitution − group ID of the
substituted user).
session$XYZ – arbitrary attribute of the current user session, where XYZ is the attribute name.
The Where Clause field content is added to the where query clause using and condition. Adding where
word is not needed, as it will be added automatically.
The Join Clause field content is added to the from query clause. It should begin with a comma, join
or left join.
The simplest constraint example is shown on the figure above: users with this constraint will see only
ref$Car entity instances that have VIN starting with '00'.
A developer can check the constraints conditions for the particular entity using the following methods of the
Security interface:
Also, it is possible to link any item tracking action with a certain constraint. The
constraintOperationType attribute should be set for the action XML element.
Example:
<table>
...
<actions>
<action id="create"/>
<action id="edit" constraintOperationType="update"/>
<action id="remove" constraintOperationType="delete"/>
</actions>
</table>
When logging in, all the attributes set for the user group and for all the groups up the hierarchy will be
placed into the user session. If an attribute is found in several levels of the hierarchy, the uppermost group
value will be used. Hence, overriding the attribute values at the lower levels of the hierarchy is not possible.
In case of the override attempt, the WARN level message will be written to the server log.
In order to create an attribute in the Access Groups screen, select the group to create the attribute for, go
to the Session Attributes tab, and click Create:
A session attribute can be accessed in the application code in the following way:
@Inject
private UserSessionSource userSessionSource;
...
Integer accessLevel = userSessionSource.getUserSession().getAttribute("accessLevel");
A session attribute can be used in the constraints as a JPQL parameter by adding the session$ prefix:
{E}.accessLevel = :session$accessLevel
1. Create a Default role, which revokes all system rights. The simplest way to do it is to create a role of
the Denying type. Select the Default role checkbox to automatically assign this role to all new users.
2. Create a set of roles for granting specific rights to different user categories. There are two strategies for
creating such roles:
Coarse-grained roles – each role has a permission set for the full range of user responsibilities in the
system. For example, Sales Manager, Accountant. Only one role is assigned to each user when
using this strategy, excluding the Default role.
Fine-grained roles – each role has a small permission set to execute specific functions within the
system. For example, Task Creator, References Editor. Each user will then be assigned
numerous roles according to their range of responsibilities.
The strategies can also be combined.Create a set of roles for granting specific rights to different user
categories. There are two strategies for creating such roles:
3. It is possible to leave the system administrator without any assigned roles, in which case, they will have
all the rights to all the system objects. Alternatively a Super type role, overriding any restriction imposed
by other roles, can be assigned.It is possible to leave the system administrator without any assigned
roles, in which case, they will have all the rights to all the system objects. Alternatively a Super type
role, overriding any restriction imposed by other roles, can be assigned.
The local administrators have access to the security subsystem screens; however they only see the users
and groups in their access group and below. Local administrators can create subgroups and users and
assign roles available in the system, however they will have at least the same constraints as the
administrator who created them.
The global administrator in the root access group should create the roles that will be available to the local
administrators for assigning to the users. The local administrators should not be able to create and update
the roles.
Problem:
The users under the Departments group should only see the users of their own group and the groups
below.
Each subgroup – Dept 1, Dept 2, etc. should have its own administrator, who can create users and
assign them the available roles.
Solution:
With this constraint, the users will not be able to see the groups higher than their own.
For the sec$User entity:
{E}.group.id in (
select h.group.id from sec$GroupHierarchy h
where h.group.id = :session$userGroupId or h.parent.id = :session$userGroupId
)
With this constraint, the users will not be able to see the users in groups higher than their own.
For the sec$Role entity:
({E}.description is null or {E}.description not like '[hide]')
With this constraint, the users will not be able to view the roles that have the [hide] string in the
description attribute.
Create a role that denies editing roles and permissions:
If the LDAP integration is enabled, a user still needs an account in the application. All the user permissions
and properties (except password) are stored in the application database, LDAP is used only for
authentication. When the user logs in to the application, the first authentication attempt is made via LDAP. If
it fails, the application tries to authenticate the user by the password hash stored in the database. As a
result, a user can log in to the system with this password even if he is not registered in LDAP or has a
different LDAP password. However, it is recommended to leave the application password empty, so that the
user could log in using the password from LDAP only (the password field in the user editor screen is not
required if the cuba.web.externalAuthentication property is set to true).
A CUBA-based application interacts with LDAP via the CubaAuthProvider interface. The platform
You can use the Jespa library with the corresponding CubaAuthProvider described in the Active
Directory Integration Using Jespa section in order to enable advanced integration with Active Directory,
including Single Sign-On for Windows domain users.
You can also create your own implementation of the CubaAuthProvider interface and use it by setting the
following application properties:
cuba.web.externalAuthentication = true
cuba.web.externalAuthenticationProviderClass = com.company.sample.web.MyAuthProvider
The following Web Client application properties are used to set up LDAP integration:
In case of the integration with Active Directory, when creating users in the application, specify their
sAMAccountName without domain as a login.
Add the following dependencies to the web module configuration section in build.gradle:
configure(webModule) {
...
dependencies {
compile('com.company.thirdparty:jespa:1.1.17') // from a custom repository
compile('jcifs:jcifs:1.3.17') // from Maven Central
...
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.Configuration;
import com.haulmont.cuba.core.global.GlobalConfig;
import com.haulmont.cuba.core.global.Messages;
import com.haulmont.cuba.core.sys.AppContext;
import com.haulmont.cuba.security.global.LoginException;
import com.haulmont.cuba.web.auth.CubaAuthProvider;
import com.haulmont.cuba.web.auth.DomainAliasesResolver;
import jespa.http.HttpSecurityService;
import jespa.ntlm.NtlmSecurityProvider;
import jespa.security.PasswordCredential;
import jespa.security.SecurityProviderException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.inject.Inject;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@Inject
private Configuration configuration;
@Inject
private Messages messages;
@SuppressWarnings("deprecation")
@Override
public void init(FilterConfig filterConfig) throws ServletException {
initDomains();
properties.put("jespa.bindstr", getBindStr());
properties.put("jespa.service.acctname", getAcctName());
properties.put("jespa.service.password", getAcctPassword());
properties.put("jespa.account.canonicalForm", "3");
properties.put("jespa.log.path",
configuration.getConfig(GlobalConfig.class).getLogDir() + "/jespa.log");
properties.put("http.parameter.anonymous.name", "anon");
fillFromSystemProperties(properties);
try {
super.init(properties);
} catch (SecurityProviderException e) {
throw new ServletException(e);
}
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain
chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if (httpServletRequest.getHeader("User-Agent") != null) {
String ua = httpServletRequest.getHeader("User-Agent").toLowerCase();
boolean windows = ua.contains("windows");
boolean gecko = ua.contains("gecko") && !ua.contains("webkit");
if (!windows && gecko) {
chain.doFilter(request, response);
return;
}
}
super.doFilter(request, response, chain);
}
@Override
public void authenticate(String login, String password, Locale loc) throws
LoginException {
DomainAliasesResolver aliasesResolver = AppBeans.get(DomainAliasesResolver.NAME);
String domain;
String userName;
userName = login;
Configure additional Jespa properties in the local.app.properties file (see Jespa Operator’s
Manual). For example:
jespa.log.level=3
If the application is deployed to Tomcat, Jespa log file can be found in tomcat/logs.
Add the server address to the local intranet in the browser settings:
For Internet Explorer and Chrome: Settings > Security > Local intranet > Sites > Advanced
For Firefox: about:config > network.automatic-ntlm-auth.trusted-
uris=http://myapp.mycompany.com
Identity Provider (IDP) is an application that provides user authentication. It contains a login form for
entering user credentials and checks the credentials against the list of registered users. Only one Identity
Provider is allowed in a SSO environment.
Service Provider (SP) is a regular application that redirects to IDP for user authentication. SP should
contain the same list of users as IDP (passwords do not matter though). SP provides authorization using
CUBA security roles and access groups. There may be any number of Service Providers in a SSO
environment.
An application can be an Identity Provider and a Service Provider at the same time, so you don’t have to
setup a dedicated IDP. The SSO functionality is provided by the cuba-idp module which is a part of the Web
Client block. You can develop your applications as usual and setup SSO just on deployment stage if
needed.
CUBA SSO uses custom HTTP-based protocol and currently does not provide integration with
systems using standard authentication protocols like SAML or OIDC.
In SSO environment, when a user enters a Service Provider URL, the SP redirects to the IDP page for
entering login name and password. After successful authentication, IDP redirects back to the SP application
and the user transparently logs in to SP.
On Identity Provider:
Add the following settings to the web.xml file of the web module (if you do it on the deployment
stage, this file is located by the following path: tomcat/webapps/app/WEB-INF/web.xml):
<servlet>
<servlet-name>idp</servlet-name>
<servlet-class>com.haulmont.idp.sys.CubaIdpServlet</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>idp</servlet-name>
<url-pattern>/idp/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>idpSpringSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>contextAttribute</param-name>
<param-
value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.idp</param-value>
</init-param>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>springSecurityFilterChain</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>idpSpringSecurityFilterChain</filter-name>
<url-pattern>/idp/*</url-pattern>
</filter-mapping>
By default, the IDP login form uses a localization mechanism based on the webL10n JavaScript library
and contains messages for English and Russian locales. In order to create messages for other
languages, create file modules/web/web/idp/l10n/locales.ini and define the list of message
files in it:
[*]
@import url(messages.properties)
[ru]
@import url(messages_ru.properties)
[es]
@import url(messages_es.properties)
You can also completely replace the login form by creating your own login.html and js/login.js
files, or modify the styles in the css/login.css file.
IDP Implementation
IDP entry points are implemented in the cuba_IdpController and cuba_IdpServiceController
Spring MVC controllers. You can create your own controllers and define them in the idp-dispatcher-
spring.xml file of your web module with the same names to provide a customized behaviour.
In the standard implementation, the IDP sessions are stored on Middleware and replicated in cluster. This
functionality is provided by the cuba_IdpSessionStore bean. You can customize the sessions storage
by creating a bean with the same name in the core module of your project and registering it in the
corresponding spring.xml file. See details in the Extending Business Logic section.
1. Both applications will be running on localhost, so start with creating aliases in your hosts file:
127.0.0.1 foo
127.0.0.1 bar
2. Create two projects in Studio and assign different sets of Tomcat ports.
Project HTTP port AJP port Shutdown port
3. In the Foo project, edit the modules/web/web/WEB-INF/web.xml file and add the following IDP
configuration:
<servlet>
<servlet-name>idp</servlet-name>
<servlet-class>com.haulmont.idp.sys.CubaIdpServlet</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>idp</servlet-name>
<url-pattern>/idp/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>idpSpringSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>contextAttribute</param-name>
<param-
value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.idp</param-value>
</init-param>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>springSecurityFilterChain</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>idpSpringSecurityFilterChain</filter-name>
<url-pattern>/idp/*</url-pattern>
</filter-mapping>
4. In the Foo project, edit the web-app.properties file of the web module and add the following
properties:
cuba.idp.serviceProviderUrls = http://foo:8081/app/,http://bar:8082/app/
cuba.idp.serviceProviderLogoutUrls = http://foo:8081/app/dispatch/idpc/logout,http:
//bar:8082/app/dispatch/idpc/logout
cuba.idp.trustedServicePassword = mdgh12SSX_pic2
cuba.webAppUrl = http://foo:8081/app/
cuba.web.externalAuthentication = true
cuba.web.externalAuthenticationProviderClass =
com.haulmont.cuba.web.auth.IdpAuthProvider
cuba.web.idp.baseUrl = http://foo:8081/app/idp/
cuba.web.idp.trustedServicePassword = mdgh12SSX_pic2
5. In the Bar project, edit the web-app.properties file of the web module and add the following
properties:
cuba.webAppUrl = http://bar:8082/app/
cuba.web.externalAuthentication = true
cuba.web.externalAuthenticationProviderClass =
com.haulmont.cuba.web.auth.IdpAuthProvider
cuba.web.idp.baseUrl = http://foo:8081/app/idp/
cuba.web.idp.trustedServicePassword = mdgh12SSX_pic2
A.1. app-component.xml
app-component.xml file is required for using the current application as a component of another
application. The file defines the dependencies on other components, describes the existing application
modules, generated artifacts and exposed configuration parameters.
The app-component.xml file should be located in a package, specified in the App-Component-Id entry
of the global module JAR manifest. This manifest entry allows the build system to find components for a
project in the build class path. As a result, in order to use some component in your project, just define the
component’s global artifact coordinates in the buildscript/dependencies section of your build.gradle.
By convention, the app-component.xml is located in the root package of the project (defined in
metadata.xml) which is also equal to the projects’s artifact group (defined in build.gradle):
App-Component-Id == root-package == cuba.artifact.group == e.g. 'com.company.sample'
Use CUBA Studio to generate the app-component.xml descriptor and the manifest entries for the current
project automatically.
A.2. context.xml
context.xml file is the application deployment descriptor for the Apache Tomcat server. In a deployed
application, this file is located in the META-INF subfolder of web application folder or the WAR file, for
example, tomcat/webapps/app-core/META-INF/context.xml. In application project, the files of this type can
be found in the /web/META-INF folders of the core, web and portal modules.
The main purpose of this file in the Middleware block is to define a datasource and assign a JNDI name,
In all blocks implemented as web applications, this file may be used to disable serialization of
HTTP-sessions:
<Manager className="org.apache.catalina.session.PersistentManager" debug="0"
distributable="false"
saveOnRestart="false">
<Store className="org.apache.catalina.session.FileStore"/>
</Manager>
A.3. datatypes.xml
datatypes.xml defines the available data types for entity attributes. See Datatype
The loading mechanism does not support extension, i.e. all data types are loaded from a single file –
either from the CLASSPATH root or from the com.haulmont.chile.core.datatypes package.
Available data types should be specified as datatype elements. The only mandatory attribute is class,
which defines a data type class implementing the Datatype interface. The remaining attribute set depends
on the class. The object created from the class, receives the corresponding XML element when created,
and should parse the element itself.
Typical attributes:
Example:
<datatypes>
<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'T'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>
A.4. dispatcher-spring.xml
The files of this type define configuration of an additional Spring Framework container for client blocks
containing Spring MVC controllers.
The additional container for controllers is created with the main container (configured by spring.xml files) as
its parent. Therefore, the beans of the controllers container can use the beans of the main container, while
the beans of the main container cannot "see" the beans of the controllers container.
The platform web and portal modules already contain such configuration files: cuba-dispatcher-
spring.xml and cuba-portal-dispatcher-spring.xml respectively. The former supports the work
of controllers for files uploading and downloading. The latter supports the REST API controllers.
If you have created Spring MVC controllers in your project (for example, in the web module), add the
following configuration:
<context:annotation-config/>
<context:component-scan base-package="com.company.sample.web.controller"/>
</beans>
Include the file into the cuba.dispatcherSpringContextConfig application property in the modules/web
/src/web-app.properties file:
cuba.dispatcherSpringContextConfig = cuba-dispatcher-spring.xml dispatcher-spring.xml
A.5. menu.xml
The files of this type are used in the application blocks implementing the generic user interface, such as the
Web Client and the Desktop Client. These files define the main menu structure of the application.
The file location is specified in the cuba.menuConfig application property. When you create a new project in
Studio, it creates the web-menu.xml file in the root package of the web module, for example
modules/web/src/com/company/sample/web-menu.xml.
menu – a folding menu containing menu items and other folding menus.
menu attributes:
id – identifier of an element, used for name localization (see below).
description - a text which is shown as a tooltip on mouse hover.
icon - icon of the menu element. See icon for details.
insertBefore, insertAfter – determines whether the item should be inserted before or after a
particular element or a menu item with specified identifier. This attribute is used to insert an element
to an appropriate place in the menu defined in files of application components. Before and after
elements cannot be used at the same time.
stylename - defines a style name for the menu item. See Themes for details.
menu elements:
menu
item – menu item (see below).
separator – separator.
item – menu item.
item attributes:
id – identifier of an element, used for caption localization (see below), and for linking to one of the UI
screen elements registered in screens.xml file. When user clicks on a menu item, the corresponding
screen will be opened in the main application window.
description - a text which is shown as a tooltip on mouse hover.
shortcut – a keyboard shortcut for this menu item. Possible modifiers – ALT, CTRL, SHIFT – are
separated with “-”. For example:
shortcut="ALT-C"
shortcut="ALT-CTRL-C"
shortcut="ALT-CTRL-SHIFT-C"
Shortcuts can also be configured in application properties and then used in menu.xml file in the
following way:
shortcut="${sales.menu.customer}"
openType – screen open mode. The following modes are available: WindowManager.OpenType:
NEW_TAB, THIS_TAB, DIALOG, NEW_WINDOW.
Default value – NEW_TAB.
NEW_WINDOW mode is only supported in the Desktop Client. For the Web Client it is equivalent to
NEW_TAB mode.
<menu-config xmlns="http://schemas.haulmont.com/cuba/menu.xsd">
</menu-config>
A localized name of a menu element is defined the following way: the menu-config prefix with a dot at the
end is added to the element identifier; the resulting string is used as a key for the main message pack. For
example:
menu-config.sales=Sales
menu-config.sales$Customer.lookup=Customers
A.6. metadata.xml
Files of this type are used to register non-persistent entities and assign meta annotations, see Metadata
Framework.
The metadata.xml file of the project is specified in the cuba.metadataConfig application property.
metadata elements:
Example:
<metadata xmlns="http://schemas.haulmont.com/cuba/metadata.xsd">
<metadata-model root-package="com.sample.sales">
<class>com.sample.sales.entity.SomeTransientEntity</class>
<class>com.sample.sales.entity.OtherTransientEntity</class>
</metadata-model>
<annotations>
<entity class="com.haulmont.cuba.security.entity.User">
<annotation
name="com.haulmont.cuba.core.entity.annotation.TrackEditScreenHistory"
value="true"/>
<annotation name="com.haulmont.cuba.core.entity.annotation.EnableRestore"
value="true"/>
</entity>
</annotations>
</metadata>
A.7. permissions.xml
Files of this type are used in the Web Client and the Desktop Client blocks for registration of specific user
permissions.
The file location is defined in the cuba.permissionConfig application property. When you create a new
project in Studio, it creates the web-permissions.xml file in the root package of the web module, for
example modules/web/src/com/company/sample/web-permissions.xml.
permission-config elements:
For example:
<permission-config xmlns="http://schemas.haulmont.com/cuba/permissions.xsd">
<specific>
<category id="app">
<permission id="app.doSomething"/>
<permission id="app.doSomethingOther"/>
</category>
</specific>
</permission-config>
A.8. persistence.xml
Files of this type are standard for JPA, and are used for registration of persistent entities and configuration
of ORM framework parameters.
The persistence.xml file of the project is defined in the cuba.persistenceConfig application property.
When the Middleware block starts, the specified files are combined into a single persistence.xml, stored
in the application work folder. File order is important, because each subsequent file in the list can override
previously defined ORM parameters.
Example of a file:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="sales" transaction-type="RESOURCE_LOCAL">
<class>com.sample.sales.entity.Customer</class>
<class>com.sample.sales.entity.Order</class>
</persistence-unit>
</persistence>
A.9. remoting-spring.xml
Files of this type configure an additional Spring Framework container for the Middleware block, used for
exporting services and other middleware components accessed by the client tier (hereafter remote access
container).
Remote access container is created with the main container (configured by spring.xml files) as its parent.
Therefore, the beans of the remote access container can use the beans of the main container, while the
beans of the main container cannot "see" the beans of the remote access container.
The primary goal of remote access is to make Middleware services accessible to the client level using the
Spring HttpInvoker mechanism. The cuba-remoting-spring.xml file in the cuba application
component defines the servicesExporter bean of RemoteServicesBeanCreator type, which
receives all service classes from the main container and exports them. In addition to regular annotated
services, remote access container exports a number of specific beans, such as LoginService.
Furthermore, the cuba-remoting-spring.xml file defines a base package that serves as a starting point
for lookup of annotated Spring MVC controller classes used for file uploading and downloading.
The remoting-spring.xml file in the application project should only be created when specific Spring
MVC controllers are used. Application project services will be imported by the standard
servicesExporter bean defined in the cuba application component.
A.10. screens.xml
Files of this type are used in the generic user interface of the Web Client and the Desktop Client for
registration of screen XML-descriptors.
The file location is specified in the cuba.windowConfig application property. When you create a new project
in Studio, it creates the web-screens.xml file in the root package of the web module, for example
modules/web/src/com/company/sample/web-screens.xml.
screen-config elements:
include attributes:
file – path to a file according to the rules of the Resources interface.
</screen-config>
A.11. spring.xml
The files of this type configure the main Spring Framework container for each application block.
The spring.xml file of the project is specified in the cuba.springContextConfig application property.
Most of the configuration of the main container is performed using bean annotations (e.g. @Component,
@Service, @Inject and others), therefore the only mandatory part of spring.xml in an application project
is the context:component-scan element, which specifies the base Java package for lookup of
annotated classes. For example:
<context:component-scan base-package="com.sample.sales"/>
The remaining configuration depends on the block that a container is being configured for, e.g. the
registration of JMX-beans for the Middleware block, or services import for client blocks.
A.12. views.xml
Files of this type are used to describe views, see Views.
views elements:
Example:
<views xmlns="http://schemas.haulmont.com/cuba/view.xsd">
<view class="com.sample.sales.entity.Order"
name="orderWithCustomer"
extends="_local">
<property name="customer" view="_minimal"/>
</view>
<view class="com.sample.sales.entity.Item"
name="itemsInOrder">
<property name="quantity"/>
<property name="product" view="_minimal"/>
</view>
<view class="com.sample.sales.entity.Order"
name="orderWithCustomerDefinedInline"
extends="_local">
<property name="customer">
<property name="name"/>
<property name="email"/>
</property>
</view>
</views>
A.13. web.xml
The web.xml file is a standard descriptor of a Java EE web application and should be created for the
Middleware, Web Client and Web Portal blocks.
In an application project, web.xml files are located in the web/WEB-INF folders of the corresponding
modules.
web.xml for the Middleware block (core project module) has the following content:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns
/javaee/web-app_3_0.xsd"
version="3.0">
<listener>
<listener-class>com.haulmont.cuba.core.sys.AppContextLoader</listener-class>
</listener>
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>com.haulmont.cuba.core.sys.remoting.RemotingServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>restapi</servlet-name>
<servlet-class>com.haulmont.cuba.core.sys.restapi.RestApiServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>restapi</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>
The context-param elements define initializing parameters for the ServletContext object of the
current web application. The list of application property files is also defined in the appPropertiesConfig
parameter.
The listener element defines a listener class implementing the ServletContextListener interface.
The Middleware block uses the AppContextLoader class as a listener. This class initializes the
AppContext.
Servlet descriptions follow, including the RemotingServlet class, mandatory for the Middleware block.
This servlet is accessible via the /remoting/* URL, and is related to the remote access container (see
remoting-spring.xml).
web.xml for the Web Client block (web project module) has the following content:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns
/javaee/web-app_3_0.xsd"
version="3.0">
<context-param>
<description>Vaadin production mode</description>
<param-name>productionMode</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>appPropertiesConfig</param-name>
<param-value>
classpath:cuba-web-app.properties
classpath:web-app.properties
file:${catalina.home}/conf/app/local.app.properties
</param-value>
</context-param>
<listener>
<listener-class>com.vaadin.server.communication.JSR356WebsocketInitializer</listener-
class>
</listener>
<listener>
<listener-class>com.haulmont.cuba.web.sys.WebAppContextLoader</listener-class>
</listener>
<servlet>
<servlet-name>app_servlet</servlet-name>
<servlet-class>com.haulmont.cuba.web.sys.CubaApplicationServlet</servlet-class>
<init-param>
<param-name>application</param-name>
<param-value>com.haulmont.sales.web.App</param-value>
</init-param>
<init-param>
<param-name>widgetset</param-name>
<param-value>com.haulmont.cuba.web.toolkit.ui.WidgetSet</param-value>
</init-param>
<init-param>
<param-name>UI</param-name>
<param-value>com.haulmont.cuba.web.AppUI</param-value>
</init-param>
<init-param>
<param-name>UIProvider</param-name>
<param-value>com.haulmont.cuba.web.sys.CubaUIProvider</param-value>
</init-param>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>app_servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>cuba_filter</filter-name>
<filter-class>com.haulmont.cuba.web.sys.CubaHttpFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>cuba_filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
The list of application property files is defined in the appPropertiesConfig parameter. The
productionMode property disables the Vaadin framework debugging mode.
Next, the CubaApplicationServlet is defined, providing the generic user interface implementation
based on the Vaadin framework. The servlet has a number of parameters, including:
Later, the CubaHttpFilter required for functioning of the Web Client block is defined.
cuba.additionalStores
Defines the names of additional data stores used in the application.
Example:
cuba.additionalStores = db1, mem1
cuba.allowQueryFromSelected
Allows the generic filter to use sequential filtering mode. See also Sequential Queries.
Interface: GlobalConfig
cuba.anonymousLogin
Login name of the user on behalf of which the anonymous session is created (see
cuba.anonymousSessionId).
Interface: ServerConfig
cuba.anonymousSessionId
Defines the UUID of the anonymous user session which is available before user login. This session is
always created automatically on the server startup. See also cuba.anonymousLogin.
Interface: GlobalConfig
cuba.automaticDatabaseUpdate
Determines whether server should run DB update scripts at application start.
Interface: ServerConfig
cuba.availableLocales
List of supported user interface languages.
Example:
cuba.availableLocales=French|fr;English|en
{language_name} – name displayed in the list of available languages. For example, such lists can be
found on the login screen and on the user edit screen.
The first language listed in the cuba.availableLocales property, will be selected in the list of
available languages by default, if the list does not contain the user’s operating system language.
Otherwise, user’s operating system language will be selected by default.
Interface: GlobalConfig
cuba.backgroundWorker.maxActiveTasksCount
The maximum number of active background tasks.
Interface: WebConfig
cuba.backgroundWorker.timeoutCheckInterval
Defines an interval in milliseconds for checking timeouts of background tasks.
Interface: ClientConfig
cuba.bruteForceProtection.enabled
Enables a mechanism for the protection against password brute force cracking.
Interface: ServerConfig
cuba.bruteForceProtection.blockIntervalSec
Blocking interval in seconds after exceeding a maximum number of failed login attempts, if the
Default value: 60
Interface: ServerConfig
cuba.bruteForceProtection.maxLoginAttemptsNumber
A maximum number of failed login attempts for the combination of user login and IP address, if the
cuba.bruteForceProtection.enabled property is on.
Default value: 5
Interface: ServerConfig
cuba.cluster.enabled
Enables interaction between Middleware servers in a cluster. See Configuring Interaction between
Middleware Servers for details.
cuba.cluster.messageSendingQueueCapacity
Limits the queue of middleware cluster messages. When the queue exceeds its maximum size, new
messages are rejected.
cuba.confDir
Defines location of the configuration folder for an application block.
Default value for the Middleware, Web Client and Web Portal blocks: ${catalina.home}/conf
/${cuba.webContextName}
In case of a standard Tomcat deployment, this points to a sub-folder with the name of the current web
app in the tomcat/conf folder, for example, tomcat/conf/app-core.
Interface: GlobalConfig
cuba.connectionReadTimeout
Sets Middleware connection timeout for client blocks. Non-negative value is passed to the
setReadTimeout() method of URLConnection.
Default value: -1
Used in the Web Client, Web Portal and Desktop Client blocks.
cuba.connectionTimeout
Sets Middleware connection timeout for client blocks. Non-negative value is passed to the
setConnectTimeout() method of URLConnection.
Default value: -1
Used in the Web Client, Web Portal and Desktop Client blocks.
cuba.connectionUrlList
Sets Middleware server connection URL for client blocks.
Property value should contain one or more comma separated URLs http[s]://host[:port]
/app-core, where host is the server hostname, port is the server port, and app-core is the name of
the the Middleware block web application. For example:
cuba.connectionUrlList = http://localhost:8080/app-core
When using a cluster of Middleware servers, their addresses should be listed, separated with commas:
cuba.connectionUrlList = http://server1:8080/app-core,http://server2:8080/app-core
The order of servers in the list, determines the order in which the client will attempt to send requests. In
the example above, the client will send request to server1 first, and if it is inaccessible, to server2. If a
request to server2 completes successfully, the client will use server2 as the primary server and will
continue working with it. Restarting the client will reset the list to the initial value. Uniform distribution of
clients across a cluster of servers can be achieved by using the cuba.randomServerPriority property.
Interface: ClientConfig
Used in the Web Client, Web Portal and Desktop Client blocks.
cuba.creditsConfig
Additive property defining a credits.xml file containing information about the software components
used by the application.
The file is loaded using the Resources interface, so it can be located in classpath or in the configuration
directory.
Example:
cuba.creditsConfig = +com/company/base/credits.xml
cuba.dataManagerChecksSecurityOnMiddleware
Indicates that DataManager should apply security restrictions when invoked on the middleware.
Interface: ServerConfig
cuba.dataSourceJndiName
Defines JNDI name of the javax.sql.DataSource object used for connection to the application
database.
cuba.dataDir
Defines the location of the work folder for an application block.
Default value for the Middleware, Web Client and Web Portal blocks: ${catalina.home}/work
/${cuba.webContextName}
In case of a standard Tomcat deployment, this points to a sub-folder with the name of the current
web-app inside the tomcat/work folder, for example, tomcat/work/app-core.
Interface: GlobalConfig
cuba.dbDir
Defines the location of the database scripts folder.
Interface: ServerConfig
cuba.dbmsType
Defines the DBMS type. Affects the choice of DBMS integration interface implementations and the search
for database init and update scripts together with cuba.dbmsVersion.
cuba.dbmsVersion
An optional property that sets the database version. Affects the choice of DBMS integration interface
implementations and the search for database init and update scripts together with cuba.dbmsType.
cuba.defaultQueryTimeoutSec
Defines default transaction timeout.
Interface: ServerConfig
cuba.desktop.useServerTime
Enables adjustment of the time provided by the TimeSource interface of the Desktop Client block.The
time will approximately equal to the time of the Middleware the client is connected to.
Interface: DesktopConfig
cuba.desktop.useServerTimeZone
Enables adjustment of the Desktop Client block’s JVM timezone to the timezone of the Middleware the
client is connected to.
Interface: DesktopConfig
cuba.disableOrmXmlGeneration
Disables automatic generation of the orm.xml file for extended entities.
Default value: false (orm.xml will be created automatically if any extended entity exist).
cuba.dispatcherSpringContextConfig
Additive property defining a dispatcher-spring.xml file of a client block.
The file is loaded using the Resources interface, so it can be located in classpath or in the configuration
directory.
Example:
cuba.dispatcherSpringContextConfig = +com/company/sample/portal-dispatcher-spring.xml
cuba.download.directories
Defines a list of folders from which the Middleware files can be downloaded from via
com.haulmont.cuba.core.controllers.FileDownloadController. For example, file
downloading is utilized by the server log display mechanism found in the Administration > Server Log
web client screen.
cuba.email.*
Email sending parameters described in Configuring Email Sending Parameters.
cuba.fileStorageDir
Defines file storage folder structure roots. For more information, see Standard File Storage
Implementation.
cuba.entityAttributePermissionChecking
If set to false, turns off checking entity attribute permissions on Middleware.
cuba.entityLog.enabled
Activates the entity log mechanism.
Interface: EntityLogConfig
cuba.groovyEvaluationPoolMaxIdle
Sets the maximum number of unused compiled Groovy expressions in the pool during
Scripting.evaluateGroovy() method execution. It is recommended to increment this parameter
when intensive execution of Groovy expressions is required, for example, for a large number of
application folders.
Default value: 8
cuba.groovyEvaluatorImport
Defines a list of classes imported by all Groovy expressions executed through Scripting.
Example:
cuba.groovyEvaluatorImport =
com.haulmont.cuba.core.global.PersistenceHelper,com.abc.sales.CommonUtils
cuba.gui.genericFilterChecking
Influences the behavior of the Filter component.
When set to true, does not allow to apply a filter without specifying parameters.
Interface: ClientConfig
cuba.gui.genericFilterControlsLayout
Sets a template for Filter controls layout. Each control has the following format: [component_name |
options-comma-separated], e.g. [pin | no-caption, no-icon].
Available controls:
filters_popup - popup button for selecting a filter, combined with the Search button.
filters_lookup - lookup field for selecting a filter. The Search button should be added as a
separate control.
search - Search button. Do not add if use filters_popup.
add_condition - link button for adding new conditions.
spacer - an empty space between controls.
settings - Settings button. Specify action names that should be displayed in Settings popup as
options (see below).
max_results - group of controls for setting maximum number of records to be selected.
fts_switch - checkbox for switching to the Full-Text Search mode.
The following actions can be used as options of the settings control: save, save_as, edit, remove,
pin, make_default, save_search_folder, save_app_folder.
The actions can also be used as independent controls outside of the Settings popup. In this case, they
can have the following options:
no-icon - if an action button should be dispalyed without an icon. For example: [save |
no-icon].
no-caption - if an action button should be dispalyed without a caption. For example: [pin |
no-caption].
Default value:
[filters_popup] [add_condition] [spacer] \
[settings | save, save_as, edit, remove, make_default, pin, save_search_folder,
save_app_folder] \
[max_results] [fts_switch]
Interface: ClientConfig
cuba.gui.genericFilterManualApplyRequired
Influences the behavior of the Filter component.
When set to true, the screens containing filters will not load the corresponding datasources
automatically, until user clicks the filter Apply button.
Interface: ClientConfig
cuba.gui.genericFilterMaxResultsOptions
Defines the options for the Show rows drop-down list of the Filter component.
NULL option indicates that the list should contain an empty value.
Interface: ClientConfig
cuba.gui.genericFilterColumnsCount
Defines the number of columns with conditions for the Filter component.
Default value: 3
Interface: ClientConfig
cuba.gui.genericFilterConditionsLocation
Defines the location of the conditions panel in the Filter component. Two locations are available: top
(above the filter control elements) and bottom (below the filter control elements).
Interface: ClientConfig
cuba.gui.genericFilterPopupListSize
Defines the number of items displayed in the popup list of the Search button. If the number of filters
exceeds this value, Show more… action is added to the popup list. The action opens a new dialog
window with a list of all possible filters.
Default value: 10
Interface: ClientConfig
cuba.gui.layoutAnalyzerEnabled
Allows you to disable the screen analyzer available in the context menu of the main window tabs and the
modal window captions.
Interface: ClientConfig
cuba.gui.lookupFieldPageLength
Defines the default number of options on one page of the drop-down list in the LookupField and
LookupPickerField components. It can be overridden for a concrete instance of the component using the
pageLength XML attribute.
Default value: 10
Interface: ClientConfig
cuba.gui.manualScreenSettingsSaving
If the property is set to true, screens will not save their settings automatically on close. In this mode, a
user can save or reset settings using the context menu which appears on clicking a screen tab or a dialog
window caption.
Interface: ClientConfig
cuba.gui.showIconsForPopupMenuActions
Enables displaying action icons in Table context menu and PopupButton items.
Interface: ClientConfig
cuba.gui.systemInfoScriptsEnabled
Enables the display of SQL-scripts for creating / updating / retrieving an entity instance in the System
Information window.
Such scripts actually show the contents of the database rows that store the selected entity instance,
regardless of security settings that may deny viewing of some attributes. That is why it is reasonable to
revoke the CUBA / Generic UI / System Information specific permission for all user roles except the
administrators, or set the cuba.gui.systemInfoScriptsEnabled to false for the whole
application.
Interface: ClientConfig
cuba.gui.useSaveConfirmation
Defines the layout of the dialog displayed when a user attempts closing a screen with unsaved changes
in datasources.
Value of true corresponds to a layout with three possible actions: Save changes, Don’t Save, Don’t
close the screen.
The value of false corresponds to a form with two options: Close the screen without saving changes,
Don’t close the screen.
Interface: ClientConfig
cuba.httpSessionExpirationTimeoutSec
Defines HTTP-session inactivity timeout in seconds.
Interface: WebConfig
cuba.idp.cookieHttpOnly
For a SSO Identity Provider, disables access to the IDP HTTP cookie from JavaScript.
Interface: IdpConfig
cuba.idp.cookieMaxAgeSec
For a SSO Identity Provider, sets a life time for the IDP HTTP cookie in seconds.
Interface: IdpConfig
cuba.idp.serviceProviderLogoutUrls
For a SSO Identity Provider, sets a list of URLs that are used to notify service providers about user logout
or session expiration. The values must be separated by commas.
For example:
cuba.idp.serviceProviderLogoutUrls = http://foo:8081/app/dispatch/idpc/logout,http:
//bar:8082/app/dispatch/idpc/logout
Interface: IdpConfig
cuba.idp.serviceProviderUrls
For a SSO Identity Provider, sets a list of service provider URLs. The values must be separated by
commas. Trailing '/' in URLs are required.
For example:
cuba.idp.serviceProviderUrls = http://foo:8081/app/,http://bar:8082/app/
Interface: IdpConfig
cuba.idp.sessionExpirationTimeoutSec
For a SSO Identity Provider, sets a timeout of IDP session inactivity in seconds.
Interface: IdpConfig
cuba.idp.sessionExpirationCheckIntervalMs
For a SSO Identity Provider, sets an interval of checking IDP session inactivity in milliseconds.
Interface: IdpConfig
cuba.idp.ticketExpirationTimeoutSec
For a SSO Identity Provider, sets a timeout of SSO ticket in seconds.
Interface: IdpConfig
cuba.idp.trustedServicePassword
For a SSO Identity Provider, sets a password which is used in server-to-server communication between
SP and IDP.
Interface: IdpConfig
cuba.inMemoryDistinct
Enables in-memory filtering of duplicate records instead of using select distinct at the database
level. Used by the DataManager.
Interface: ServerConfig
cuba.jmxUserLogin
Defines a user login that should be used for system authentication.
cuba.localeSelectVisible
Disables the user interface language selection when logging in.
If cuba.localeSelectVisible is set to false, the locale for a user session is selected in the following
way:
If the User entity instance has a language attribute defined, the system will use this language.
If the user’s operating system language is included in the list of available locales (set by the
cuba.availableLocales property), the system will use this language.
Otherwise, the system will use the first language defined in the cuba.availableLocales property.
Interface: GlobalConfig
cuba.logDir
Defines the location of the log folder for an application block.
Default value for the Middleware, Web Client and Web Portal blocks: ${catalina.home}/logs
In case of a standard Tomcat deployment, this property points to the tomcat/logs folder.
Interface: GlobalConfig
cuba.mainMessagePack
Additive property defining a main message pack for an application block.
The value may include a single pack or a list of packs separated with spaces.
Example:
cuba.maxUploadSizeMb
Maximum file size (in megabytes) that can be uploaded using the FileUploadField and
FileMiltiUploadField components.
Default value: 20
Interface: ClientConfig
cuba.menuConfig
Additive property defining a menu.xml file.
The file is loaded using the Resources interface, so it can be located in classpath or in the configuration
directory.
Example:
cuba.menuConfig = +com/company/sample/web-menu.xml
cuba.metadataConfig
Additive property defining a metadata.xml file.
The file is loaded using the Resources interface, so it can be located in classpath or in the configuration
directory.
Example:
cuba.metadataConfig = +com/company/sample/metadata.xml
cuba.passwordEncryptionModule
Defines the name of the bean used for user password hashing.
cuba.passwordPolicyEnabled
Enables password policy enforcement. If the property is set to true, all new user passwords will be
checked according to the cuba.passwordPolicyRegExp property.
Interface: ClientConfig
Used in the client blocks: Web Client, Web Portal, Desktop Client.
cuba.passwordPolicyRegExp
Defines a regular expression used by the password checking policy.
Default value:
((?=.*\\d)(?=.*\\p{javaLowerCase}) (?=.*\\p{javaUpperCase}).{6,20})
The expression above ensures that password contains from 6 to 20 characters, uses numbers and Latin
letters, contains at least one number, one lower case and one upper case letter. More information on
regular expression syntax is available at https://en.wikipedia.org/wiki/Regular_expression and
http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html.
Interface: ClientConfig
Used in the client level blocks: Web Client, Web Portal, Desktop Client.
cuba.permissionConfig
Additive property defining a permissions.xml file.
Example:
cuba.permissionConfig = +com/company/sample/web-permissions.xml
cuba.persistenceConfig
Additive property defining a persistence.xml file.
The file is loaded using the Resources interface, so it can be located in classpath or in the configuration
directory.
Example:
cuba.persistenceConfig = +com/company/sample/persistence.xml
cuba.portal.anonymousUserLogin
Defines a user login that should be used for anonymous session in the Web Portal block.
The user with the specified login should exist in the security subsystem and should have the required
permissions. User password is not required, because anonymous portal sessions are created via the
loginTrusted() method with the password defined in the cuba.trustedClientPassword property.
Interface: PortalConfig
cuba.queryCache.enabled
If set to false, the query cache functionality is disabled.
Interface: QueryCacheConfig
cuba.queryCache.maxSize
Maximum number of query cache entries. A cache entry is defined by the query text, query parameters,
As the cache size grows close to the maximum, the cache evicts entries that are less likely to be used
again.
Interface: QueryCacheConfig
cuba.randomServerPriority
Enables random selection of a Middleware server to connect to in a cluster, to ensure uniform distribution
of clients between servers.
cuba.remotingSpringContextConfig
Additive property defining a remoting-spring.xml file of the Middleware block.
The file is loaded using the Resources interface, so it can be located in classpath or in the configuration
directory.
Example:
cuba.remotingSpringContextConfig = +com/company/sample/remoting-spring.xml
cuba.rest.productionMode
Activates the REST API production mode that does not return exception text to client.
Interface: RestConfig
cuba.rest.apiVersion
Defines the REST API version. If the value is set to 1, the REST API of the platform versions before 5.4 is
used. Setting the value to 2 enables the new REST API version with support for service calls.
Default value: 2
Interface: RestConfig
cuba.restApiUrl
URL to the application’s REST API.
Interface: GlobalConfig
cuba.restServicesConfig
Additive property defining a file that contains a list of services available for application REST API calls.
The file is loaded using the Resources interface, so it can be located in classpath or in the configuration
directory.
Example:
cuba.restServicesConfig = +com/company/sample/app-rest-services.xml
cuba.rest.allowedOrigins
Defines a comma-separated list of origins that can access the REST API Version 2.
Default value: *
cuba.rest.anonymousEnabled
Enables an anonymous access to the REST API Version 2 endpoints.
cuba.rest.client.id
Defines an identifier of the REST API client. Client in this case is not a platform user, but an application
(some web portal or mobile app) that uses REST API Version 2. Client credentials are used for basic
authentication when accessing the REST API token endpoint.
cuba.rest.client.secret
Defines a password of the REST API client. Client in this case is not a platform user, but an application
(some web portal or mobile app) that uses REST API Version 2. Client credentials are used for basic
authentication when accessing the REST API token endpoint.
cuba.rest.client.tokenExpirationTimeSec
Defines a REST API Version 2 token expiration timeout for the default client in seconds.
cuba.rest.maxUploadSize
Maximum file size (in bytes) that can be uploaded with the REST API Version 2.
cuba.rest.servicesConfig
Additive property defining a file that contains a list of services available for application REST API Version
2 calls.
The file is loaded using the Resources interface, so it can be located in classpath or in the configuration
directory.
Example:
cuba.rest.servicesConfig = +com/company/sample/app-rest-services.xml
cuba.rest.queriesConfig
Additive property defining a file that contains a list of JPQL queries available for application REST API
Version 2 calls.
The file is loaded using the Resources interface, so it can be located in classpath or in the configuration
directory.
Example:
cuba.rest.queriesConfig = +com/company/sample/app-rest-queries.xml
cuba.schedulingActive
Enables the CUBA scheduled tasks mechanism.
Interface: ServerConfig
cuba.serialization.impl
Specifies an implementation of the Serialization interface which is used for serialization of objects
transferred between the application blocks. The platform contains two implementations:
cuba.springContextConfig
Additive property defining a spring.xml file in all standard application blocks.
The file is loaded using the Resources interface, so it can be located in classpath or in the configuration
directory.
Example:
cuba.springContextConfig = +com/company/sample/spring.xml
cuba.supportEmail
Specifies an email address to which exception reports from the default exception handler screen, as well
as user messages from the Help > Feedback screen will be sent.
Report button in the exception handler screen will be hidden, if this property is set to an empty string.
In order to successfully send emails, the parameters described in Configuring Email Sending Parameters
must also be configured.
Interface: WebConfig
cuba.tempDir
Defines the location of the temporary directory for an application block.
The default value for the Middleware, Web Client, Web Portal blocks: ${catalina.home}/temp
/${cuba.webContextName}
In case of a standard Tomcat deployment, this points to a sub-folder with the name of the current
web-app in the tomcat/temp folder, for example, tomcat/temp/app-core.
Interface: GlobalConfig
cuba.themeConfig
Defines a set of *-theme.properties files that store theme variables, such as default popup window
dimensions and input field width.
The property takes a list of files separated by spaces. The files are loaded as defined by the Resources
interface.
cuba.triggerFilesCheck
Enables the processing of bean invocation trigger files.
The trigger file is a file that is placed in the triggers subdirectory of the application block’s temporary
directory. The trigger file name consists of two parts separated with a period. The first part is the bean
name, the second part is the method name of the bean to invoke. For example:
cuba_Messages.clearCache. The trigger files handler monitors the folder for new trigger files, invokes
the appropriate methods and then removes the files.
By default, the trigger files processing is configured in the cuba-web-spring.xml file and performed
for the Web Client block only. At the project level, the processing for other modules can be performed by
periodically invoking the process() method of the cuba_TriggerFilesProcessor bean.
cuba.triggerFilesCheckInterval
Defines the period in milliseconds of trigger files processing if the cuba.triggerFilesCheck is set to true.
cuba.trustedClientPassword
Defines password used by the LoginService.loginTrusted() method. The Middleware layer can
authenticate users who connect via the trusted client block without checking the user password.
This property is used when user passwords are not stored in the database, while the client block
performs the actual authentication itself. For example, by integrating with Active Directory.
cuba.trustedClientPermittedIpList
Defines the list of IP addresses, from which the invocation of the LoginService.loginTrusted()
method is allowed. For example:
cuba.trustedClientPermittedIpList = 127.0.0.1, 10.17.*.*
Interfaces: ServerConfig
cuba.uniqueConstraintViolationPattern
A regular expression which is used to find out that the exception is caused by a database unique
constraint violation. The first or second group of the expression must return the constraint name. For
example:
ERROR: duplicate key value violates unique constraint "(.+)"
The constraint name can be used to display a localized message that indicates what entity is concerned.
For this the main message pack should contain keys equal to constraint names. For example:
IDX_SEC_USER_UNIQ_LOGIN = A user with the same login already exists
This property allows you to define a reaction to unique constraint violations depending on DBMS locale
and version.
cuba.useCurrentTxForConfigEntityLoad
Enables using current transaction, if there is one at the moment, for loading entity instances via the
configuration interfaces. This could have a positive impact on performance. Otherwise, a new transaction
is always created and committed, and the detached instances are returned.
cuba.useLocalServiceInvocation
When set to true, and Tomcat fast deployment is used, the Web Client and Web Portal blocks invoke the
Middleware services locally, bypassing the network stack, which has a positive impact on system
performance. This property should be set to false for all other deployment options.
cuba.user.fullNamePattern
Defines the full name pattern for user.
The full name pattern can be formed from the user’s first, last and middle names. The following rules
apply to the pattern:
cuba.user.namePattern
Defines the display name pattern for the User entity. The display name is used in different places,
including the upper right corner of the system’s main window.
{0} is substituted with the login attribute, {1} – with the name attribute.
cuba.userSessionExpirationTimeoutSec
Defines the user session expiration timeout in seconds.
Interface: ServerConfig
cuba.userSessionProviderUrl
Defines the Middleware block URL used for logging users in.
This parameter should be set in additional middleware blocks that execute client requests, but do not
share the user session cache. If there is no required session in the local cache at the start of the request,
this block invokes the LoginService.getSession() method at the specified URL, and caches the
retrieved session.
Interface: ServerConfig
cuba.viewsConfig
Additive property defining a views.xml file. See Views.
The file is loaded using the Resources interface, so it can be located in classpath or in the configuration
directory.
Example:
cuba.viewsConfig = +com/company/sample/views.xml
cuba.webAppUrl
Defines URL of the Web Client application.
Interface: GlobalConfig
cuba.windowConfig
Additive property defining a screens.xml file.
The file is loaded using the Resources interface, so it can be located in classpath or in the configuration
directory.
Example:
cuba.windowConfig = +com/company/sample/web-screens.xml
cuba.web.allowHandleBrowserHistoryBack
Enables handling of browser Back button in the application if the login and/or main window implements
the CubaHistoryControl.HistoryBackHandler interface. If the property is true, the standard
browser behavior is replaced with this method invocation.
Interface: WebConfig
cuba.web.appFoldersRefreshPeriodSec
Defines application folders refresh period in seconds.
Interface: WebConfig
cuba.web.appWindowMode
Determines the initial mode for the main application window – tabbed or single screen (TABBED or
SINGLE respectively). In single screen mode, screens opened in the NEW_TAB modewill replace the
current screen, instead of opening a new tab. This can be useful for simple applications and
inexperienced users.
The user is able to change the mode later, using the Help > Settings screen.
Interface: WebConfig
cuba.web.externalAuthentication
Indicates that the authentication is done through an external mechanism like LDAP or SSO Identity
Provider. See also cuba.web.externalAuthenticationProviderClass.
Interface: WebAuthConfig
cuba.web.externalAuthenticationProviderClass
A class implementing the CubaAuthProvider interface which is used when
cuba.web.externalAuthentication is set to true.
See Integration with LDAP and Single-Sign-On for CUBA Applications sections for examples.
Interface: WebAuthConfig
cuba.web.foldersPaneDefaultWidth
Defines default width (in pixels) for the folders panel.
Interface: WebConfig
cuba.web.foldersPaneEnabled
Enables the folders panel functionality.
Interface: WebConfig
cuba.web.foldersPaneVisibleByDefault
Determines whether the folders panel should be expanded by default.
Interface: WebConfig
cuba.web.idp.baseUrl
For a SSO Service Provider, sets the URL of an Identity Provider. Standard CUBA IDP uses the idp/
path (the trailing / is requred).
For example:
cuba.web.idp.baseUrl = http://main:8080/app/idp/
Interface: WebAuthConfig
cuba.web.idp.trustedServicePassword
For a SSO Service Provider, sets a password which is used in server-to-server communication between
SP and IDP. Must be equal to cuba.idp.trustedServicePassword.
Interface: WebAuthConfig
cuba.web.linkHandlerActions
Defines a list of URL commands handled by the LinkHandler bean. See Screen Links for more
information.
Interface: WebConfig
cuba.web.loginDialogDefaultUser
Defines default user name, which will be automatically populated in the login screen. This is very
convenient during development. This property should be set to <disabled> value in production
environment.
Interface: WebConfig
cuba.web.loginDialogDefaultPassword
Defines default user password, which will be automatically populated in the login screen. This is very
convenient during development. This property should be set to <disabled> value in production
environment.
Interface: WebConfig
cuba.web.loginDialogPoweredByLinkVisible
Set to false to hide the "powered by CUBA Platform" link on the login dialog.
Interface: WebConfig
cuba.web.maxTabCount
Defines the maximum number of tabs that can be opened in the main application window. The value of 0
disables this limitation.
Default value: 7
Interface: WebConfig
cuba.web.pushEnabled
Allows you to completely disable server push. The Background Tasks mechanism will not work in this
case.
Interface: WebConfig
cuba.web.pushLongPolling
Enables switching to long polling instead of WebSocket for server push implementation.
Interface: WebConfig
cuba.web.rememberMeEnabled
Enables displaying Remember Me checkbox in the standard login screen of the web client.
Interface: WebConfig
cuba.web.resourcesRoot
Sets a directory for loading files to display by Embedded component. For example:
cuba.web.resourcesRoot = ${cuba.confDir}/resources
Interface: WebConfig
cuba.web.showBreadCrumbs
Enables hiding of the breadcrumbs panel which normally appears on top of the main window working
area.
Interface: WebConfig
cuba.web.showFolderIcons
Enables the folders panel icons. When enabled, the following application theme files are used:
Interface: WebConfig
cuba.web.table.cacheRate
Adjusts Table caching in the web browser. The amount of cached rows will be cacheRate multiplied with
pageLength both below and above visible area.
Default value: 2
Interface: WebConfig
cuba.web.table.pageLength
Sets the number of rows to be fetched from the server into the web browser when Table is rendered first
time on refresh. See also cuba.web.table.cacheRate.
Default value: 15
Interface: WebConfig
cuba.web.theme
Defines the name of the theme used as default for the web client. See also cuba.themeConfig.
Interface: WebConfig
cuba.web.useFontIcons
If this property is enabled for Halo theme, Font Awesome glyphs will be used for standard actions and
platform screens instead of images.
The correspondence between the name in the icon attribute of a visual component or action and font
element is defined in the halo-theme.properties file of the platform. Keys with cuba.web.icons
prefixes correspond to icon names, and their values - to com.vaadin.server.FontAwesome
enumeration constants. For example, a font element for the standard create action is defined as follows:
cuba.web.icons.create.png = FILE_O
Interface: WebConfig
cuba.web.useInverseHeader
Controls the web client application header for Halo theme and its inheritors. If true, the header will be
dark (inverse), if false - the header takes the colour of the main application background.
property is set in the application theme. This makes sense for a dark theme, if the user has the option to
choose between a light and a dark theme. In this case, the header will be inverse for the light theme, and
the same as the main background in the dark theme.
Interface: WebConfig
cuba.web.viewFileExtensions
Defines a list of file extensions displayed in the browser when downloading the file using
ExportDisplay.show(). The | character should be used to separate the list items.
Interface: WebConfig
cuba.webContextName
Defines the web application context name. It is usually equivalent to the name of the directory or WAR-file
containing this application block.
Interface: GlobalConfig
For example, for the Middleware block, located in tomcat/webapps/app-core and available at
http://somehost:8080/app-core, the property should be set to the following value:
cuba.webContextName = app-core
cuba.webHostName
Defines the host name of the machine, on which this application block is running.
Interface: GlobalConfig
For example, for the Middleware block, available at http://somehost:8080/app-core, the property
should be set to the following value:
cuba.webHostName = somehost
cuba.webPort
Defines the port, on which this application block is running.
Interface: GlobalConfig
For example, for the Middleware block, available at http://somehost:8080/app-core, this property
should be set to the following value:
cuba.webPort = 8080
System properties can be specified at JVM startup, using the command line argument -D. Additionally,
system properties can be read or set using the getProperty() and setProperty() methods of the
System class.
You can use system properties to set or override values of application properties. For example, the following
command line argument will override the value of the cuba.connectionUrlList property which is normally set
in the web-app.properties file:
-Dcuba.connectionUrlList=http://somehost:8080/app-core
Keep in mind, that system properties affect the whole JVM, i.e all application blocks running on the
JVM will get the same value of a property.
Below are the system properties that are used by the platform but are not application properties.
logback.configurationFile
Defines the location of the Logback framework configuration file.
For application blocks running on the Tomcat web server, this system property is configured in the
tomcat/bin/setenv.bat and tomcat/bin/setenv.sh files. By default, it points to the
tomcat/conf/logback.xml configuration file.
For the Desktop Client block, the application code configures this property if it is not defined at JVM
startup. By default it points to the cuba-logback.xml file in the CLASSPATH root. See Setting up
Logging in The Desktop Client.
cuba.desktop.home
For the Desktop Client block, this property defines the root for the folders defined in the cuba.confDir,
cuba.logDir, cuba.tempDir, and cuba.dataDir application properties.
If this property is not defined at JVM startup, the default value of ${user.home}/.haulmont/cuba will be
used. A different location can be specified by overriding the getDefaultHomeDir() method of the
com.haulmont.cuba.desktop.App class.
cuba.unitTestMode
This system property is set to true when the CubaTestCase base class is running integration tests.
Example:
if (!Boolean.valueOf(System.getProperty("cuba.unitTestMode")))
return "Not in test mode";
9. Glossary
Application Tiers
See Application Tiers and Blocks.
Application Properties
Application properties are named data values of various types that define different aspects of application
configuration or functions. See Application Properties.
Application Blocks
See Application Tiers and Blocks.
Artifact
In the context of this manual, an artifact is a file (usually a JAR or ZIP file) that contains executable code
or other code obtained as a result of building a project. An artifact has a name and a version number
defined according to specific rules and can be stored in the artifact repository.
Artifact Repository
A server that stores artifacts in a specific structure. The artifacts that the project depends on are loaded
from the repository when that project is built.
Base Projects
The same as application components. This term was used in the previous versions of the platform and
documentation.
Container
Containers control lifecycle and configuration of application objects. This is a base component of the
dependency injection mechanism also known as Inversion of Control.
CUBA platform uses the Spring Framework container. For extra information, please refer to Further
Reading.
DB
A relational database.
Datasource
See Datasources.
Dependency Injection
Also known as Inversion of Control (IoC) principle. A mechanism for retrieving links to the objects being
used, which assumes that an object should only declare which objects it depends on, while the container
creates all the necessary objects and injects them in the dependent object.
See http://en.wikipedia.org/wiki/Dependency_injection.
Eager Fetching
Loading data from subclasses and related objects together with the requested entity.
Entity
Main element of the data model, see Data Model.
Entity Browser
A screen containing a table with a list of entities and buttons to create, edit and delete entities.
EntityManager
A middle tier component for working with persistent entities.
See EntityManager.
Interceptor
An element of aspect-oriented programming that enables changing or extending object method
invocations.
See http://en.wikipedia.org/wiki/Interceptor_pattern.
JMX
Java Management Extensions − a technology that provides tools to manage applications, system objects
and devices. Defines the standard for JMX-components.
JPA
Java Persistence API – a standard specification of the object-relational mapping technology (ORM).
CUBA platform uses EclipseLink framework that implements this specification.
JPQL
Platform independent object-oriented query language, defined as a part of the JPA specification.
Lazy loading
See Lazy Loading.
Local attribute
An entity attribute that is not a reference or a collection of references to other entities. Values of all local
entity attributes are typically stored in one table (with the exception of certain entity inheritance
strategies).
Managed Beans
Components that contain application business logic.
MBeans
Managed Beans that have a JMX-interface. Typically, such beans have an internal state (e.g. cache,
configuration data or statistics) that needs to be accessible through JMX.
Middleware
Middle tier – the application tier that contains the business logic, works with the database and provides a
common interface for higher client tier of an application.
Optimistic locking
Optimistic locking – an approach to managing access to shared data by different users that assumes a
very low probability of simultaneous access to the same entity instance. With this approach, locking itself
is not applied, instead the system checks if a newer version of the data is available in the database at the
moment when the changes are being saved. If so, an exception is thrown and the user must reload the
entity instance.
ORM
Object-Relational Mapping – a technology that links tables in a relational database to objects of a
programming language.
Persistent context
A set of entity instances loaded from the database or just created. Persistent context serves as data
cache within the current transaction. When transaction is committed, all persistent context entity changes
are saved to a database.
See EntityManager.
Screen Controller
A Java class containing screen initialization and event handling logic. Works in conjunction with screen’s
XML-descriptor.
Services
Middleware services provide the business interface for client calls and form the Middleware boundary.
Services can encapsulate the business logic or delegate the execution to Managed Beans.
See Services.
Soft deletion
See Soft Deletion.
UI
User Interface.
View
See Views
XML-descriptor
An XML file containing layout of visual components and datasources for a screen.
See XML-Descriptor.