Migration from CUBA Platform
Overview
Jmix framework is a direct successor of CUBA platform. It offers the same rapid application development approach but with modernized infrastructure. You can think of Jmix as a new major version of CUBA, so if your CUBA project is in active development, consider upgrading to Jmix. To facilitate this process, we provide a compatibility module with a set of CUBA APIs and features that were changed or removed in Jmix. Also, Jmix Studio contains an advanced procedure for converting a CUBA project into Jmix one.
In this chapter, we describe the main differences between Jmix and CUBA and give you detailed instructions on how to migrate your CUBA project to Jmix.
Differences from CUBA
Project Structure
A CUBA project consists of at least three modules: global
, core
, web
. In Jmix, project has a single module by default.
In a CUBA project, source files of all types (Java, Kotlin, XML, properties) are mixed in the same directories under the single source root (one per module). Jmix project follows the standard Maven directory layout when there are separate roots for files of different types:
-
src/main
- sources of production code -
src/test
- test sources -
src/main/java
,src/test/java
- for Java classes -
src/main/kotlin
,src/test/kotlin
- for Kotlin files -
src/main/resources
,src/test/resources
- for XML and property files.
Deployment Structure
A CUBA application is built into two separate web applications: core
and web
. Even if you use a "single WAR" or "uber JAR" deployment, these artifacts actually contain the core
and web
classes isolated in different classloaders and running in separate Spring contexts.
A Jmix application is a single web application built as an executable JAR or as a WAR using standard Spring Boot Gradle deployment tasks.
Application Properties
Due to the separate deployment of CUBA modules, a CUBA project properties are set in two files: app.properties
for the core
module and web-app.properties
for the web
module. These files are located in the base package of the corresponding module.
In Jmix, all properties are defined in a single application.properties
file located in the src/main/resources
root.
Spring Configuration
In CUBA, Spring container configuration is defined in spring.xml
for the core
module and web-spring.xml
for the web
module.
Jmix uses only Java-based container configuration. Spring Boot automatically scans all classes in the base package of the application (where the class annotated with @SpringBootApplcation
is located) and packages below it for Spring annotations.
Data Model
CUBA data model entities have to extend one of the base classes (StandardEntity
, BaseUuidEntity
, BaseGenericIdEntity
, etc.) and optionally implement some interfaces (Versioned
, Creatable
, Updatable
, SoftDelete
).
Jmix doesn’t force you to inherit your entities from the framework’s base classes. Instead, you should just annotate your class with @JmixEntity
and that’s it - the framework will enhance the class' bytecode to implement the features previously implemented by the CUBA entity base classes. Optional behavior (see traits) is also declared with annotations like @CreatedBy
, @CreatedDate
, etc. Of course, you can create your own base classes with required characteristics on the project level. The jmix-cuba
compatibility module provides a set of base classes equivalent to the CUBA ones, so you don’t have to rewrite your entities when migrating to Jmix.
CUBA JPA entities have to be registered in the persistence.xml
file located in the base package of the global
module. In Jmix, the persistence.xml
file is created automatically at build time by the Jmix Gradle plugin. The plugin scans the classpath and writes all @JmixEntity
-annotated classes into <base-package>/persistence.xml
inside the application JAR/WAR file.
CUBA non-persistent entities and custom datatypes are registered in the metadata.xml
file. There is no such file in Jmix:
-
DTO entities are recognized just by the
@JmixEntity
annotation. -
Custom datatype classes are annotated with
@DatatypeDef
which makes them Spring beans and allow the framework to find and register them at startup.
DataManager
The io.jmix.core.DataManager
bean in Jmix core is very similar to the TransactionalDataManager
in CUBA: it joins an existing transaction and has the save()
method instead of commit()
. But it has a crucial difference in handling security permissions: in Jmix, io.jmix.core.DataManager
checks resource and row-level policies by default, and there is separate io.jmix.core.UnconstrainedDataManager
that bypasses all access control rules. See details in the Authorization section.
The jmix-cuba
compatibility module provides the com.haulmont.cuba.core.global.DataManager
bean that has the same interface and behavior as in CUBA with respect to security: it checks only row-level policies and has the secure()
method which returns a DataManager
implementation that checks also the resource policies.
Transactions
Unlike CUBA, Jmix doesn’t provide any specific interfaces for transaction management. You should use either declarative transaction demarcation with the @Transactional
annotation on bean methods, or programmatic management with Spring’s TransactionTemplate
class.
The jmix-cuba
compatibility module provides the com.haulmont.cuba.core.Persistence
and com.haulmont.cuba.core.Transaction
interfaces that save you from rewriting your business logic working with CUBA APIs.
EntityManager
In Jmix, code working with JPA should use the standard JPA EntityManager
, Query
and TypedQuery
interfaces. The EntityManager
is obtained using injecton with @PersistenceContext
annotation, for example:
@PersistenceContext
private EntityManager entityManager;
With the jmix-cuba
compatibility module, you can keep using com.haulmont.cuba.core.EntityManager
obtained through com.haulmont.cuba.core.Persistence
.
EntityPersistingEvent
The CUBA’s EntityPersistingEvent
is superseded by EntitySavingEvent which is sent both on persisting new entities and updating existing ones. The old EntityPersistingEvent
is provided by the jmix-cuba
compatibility module and is sent only when saving new instances as it was in CUBA.
EntitySavingEvent and EntityPersistingEvent are sent only when saving entities using DataManager . If you persist entities using EntityManager , EntityPersistingEvent is not sent, regardless of the presence of the jmix-cuba module.
|
Fetch Plans and Lazy Loading
Jmix supports partial loading of entities by using fetch plans, which are the same as views in CUBA. In addition, Jmix also supports lazy loading of references for JPA entities loaded using DataManager
, so use of fetch plans is optional and should be considered as a performance optimization. See details in the Fetching Data section.
The jmix-cuba
module provides the com.haulmont.cuba.core.global.View
, ViewBuilder
and ViewRepository
classes for backward compatibility.
Security
Jmix resource roles and resource policies are very similar to CUBA roles and permissions. The main difference is how they are defined in design time: CUBA roles use classes, Jmix roles use interfaces.
Jmix row-level roles have the same purpose as CUBA access group constraints, but there are significant differences:
-
Jmix row-level roles are stored in a plain list instead of a hierarchy;
-
a user can have any number of row-level roles;
-
there is no equivalent for predefined session attributes of access groups.
The Studio migration procedure converts CUBA design-time roles into Jmix resource roles automatically. Access groups and constraints have to be converted manually, see Changed APIs section for details.
The migration procedure will preserve your list of users in the database, but all runtime security configuration (roles, policies, role assignments) will have to be done from scratch. |
Session Attributes
Jmix provides the SessionData bean for sharing values across multiple requests from the same connected user.
For backward compatibility, the jmix-cuba
module provides the com.haulmont.cuba.security.global.UserSession
class that delegates its getAttribute()
/ setAttribute()
methods to SessionData
.
Features Removed in Jmix
Below is a list of CUBA features that were removed in Jmix without replacement.
-
Attribute access control on the
DataManager
level. Entity attribute permissions are now considered only when displaying data in UI components and returning entities via REST API endpoints. See Data Access Checks. -
State-based entity attribute access control with
SetupAttributeAccessHandler
andSetupAttributeAccessHandler
. -
Screen component permissions.
-
Session attributes defined in the Access Groups.
-
ClusterManagerAPI
interface and its implementation. -
Editor screen opening history and
@TrackEditScreenHistory
annotation. -
Support for Microsoft SQL Server 2005 with
net.sourceforge.jtds.jdbc.Driver
.
Changed APIs
Below is a list of changed APIs that are not converted by the Studio automatic migration and have no compatibility wrappers in jmix-cuba
module. Use this information when fixing your code for compilation.
Access groups and constraints
Convert the annotated class to an interface. The interface methods should return void
and are used merely for grouping annotations. See details in the Row-level Roles section.
-
com.haulmont.cuba.security.app.group.annotation.AccessGroup
→io.jmix.security.role.annotation.RowLevelRole
-
com.haulmont.cuba.security.app.group.annotation.JpqlConstraint
→io.jmix.security.role.annotation.JpqlRowLevelPolicy
-
com.haulmont.cuba.security.app.group.annotation.Constraint
→io.jmix.security.role.annotation.PredicateRowLevelPolicy
.
Security configuration entities
Below are rough equivalents of entities used to configure security at runtime:
-
com.haulmont.cuba.security.entity.Role
→io.jmix.securitydata.entity.ResourceRoleEntity
-
com.haulmont.cuba.security.entity.Group
→io.jmix.securitydata.entity.RowLevelRoleEntity
-
com.haulmont.cuba.security.entity.UserRole
→io.jmix.securitydata.entity.RoleAssignmentEntity
-
com.haulmont.cuba.security.entity.Permission
→io.jmix.securitydata.entity.ResourcePolicyEntity
-
com.haulmont.cuba.security.entity.Constraint
→io.jmix.securitydata.entity.RowLevelPolicyEntity
Multitenancy
After running the automatic migration procedure, follow the steps below.
-
Add the
StandardTenantEntity
to your project:package com.company.app.entity; // replace with your base package import com.haulmont.cuba.core.entity.StandardEntity; import io.jmix.core.annotation.TenantId; import io.jmix.core.metamodel.annotation.JmixEntity; import javax.persistence.Column; import javax.persistence.MappedSuperclass; @MappedSuperclass @JmixEntity public abstract class StandardTenantEntity extends StandardEntity { private static final long serialVersionUID = -1215037188627831268L; @TenantId @Column(name = "TENANT_ID") protected String tenantId; public void setTenantId(String tenantId) { this.tenantId = tenantId; } public String getTenantId() { return tenantId; } }
In all entities extended from the CUBA
StandardTenantEntity
, replace the import ofcom.haulmont.addon.sdbmt.entity.StandardTenantEntity
to the import of your ownStandardTenantEntity
. -
In the
User
entity, implement theAcceptsTenant
interface and add thetenant
attribute annotated with@TenantId
and mapped to theSYS_TENANT_ID
column:public class User implements JmixUserDetails, HasTimeZone, AcceptsTenant { // ... @TenantId @Column(name = "SYS_TENANT_ID") private String tenant; public String getTenant() { return tenant; } public void setTenant(String tenant) { this.tenant = tenant; } @Override public String getTenantId() { return tenant; } }
-
Add
tenant
attribute to the user browse and edit screens as described in items 3, 4, 5 of the Multitenancy / Configuring Users section. -
Rename
CUBASDBMT_TENANT
table toMTEN_TENANT
using the following Liquibase changeset (it’s needed only in Jmix 1.1.0, becausejmix-cuba
module in Jmix 1.1.1+ contains this changeset):<changeSet id="10" author="me"> <preConditions onFail="MARK_RAN"> <tableExists tableName="CUBASDBMT_TENANT"/> </preConditions> <renameTable oldTableName="CUBASDBMT_TENANT" newTableName="MTEN_TENANT"/> </changeSet>
Reports
-
com.haulmont.reports.app.service.ReportService
,com.haulmont.reports.gui.ReportGuiManager
→io.jmix.reports.runner.ReportRunner
Entity snapshots
-
com.haulmont.cuba.core.app.EntitySnapshotService
→io.jmix.audit.snapshot.EntitySnapshotManager
-
com.haulmont.cuba.gui.app.core.entitydiff.EntityDiffViewer
→io.jmix.auditui.screen.snapshot.SnapshotDiffViewer
-
<frame id="diffFrame" src="/com/haulmont/cuba/gui/app/core/entitydiff/diff-view.xml"/>
→<fragment id="diffFrame" screen="snapshotDiff"/>
How To Migrate
Jmix Studio provides an automatic procedure for converting a CUBA project into Jmix one. It creates a new project with a standard Jmix template and then copies the source code from your CUBA project into the new structure inside the new Jmix project. While copying, Studio makes a lot of changes in the source files: replaces packages and known framework classes, converts screen XML descriptors to the new schema, configures your database connections, adds dependencies to the new add-ons. After the migration procedure completes, you should fix the remaining problems manually.
The migration procedure keeps your CUBA project untouched, so it’s safe to run the procedure on any working copy of a project. |
There are the following limitations of the automatic migration:
|
In Jmix Studio v.1.1.4 and below, the migration procedure may fail if your IntelliJ IDEA contains Kotlin plugin of a version newer than 1.5.10. In such a case, downgrade Kotlin plugin to 1.5.10 or below. In Jmix Studio v.1.1.5 and above, the migration does not have a dependency on Kotlin plugin. |
Main Migration
Follow the steps below to run the automatic migration procedure.
-
Open your CUBA project in Jmix Studio.
-
Wait until the project is imported and fully indexed. Watch the IDE progress bar and wait until it stops displaying new messages.
-
You should see a notification about migrating to Jmix in the bottom right corner. Click Migrate or select File → New → Jmix project from this CUBA project in the main menu of the IDE.
The notification could not appear if the project was opened and imported to the IDE before. In this case, click Reload All Gradle Projects button in the Gradle tool window.
-
Studio starts the New Jmix project wizard.
-
Select the latest Jmix version (at least 1.1.0) and the project JDK used in your CUBA project. Click Next.
-
On the next step of the wizard, enter the new Jmix project name and location. Click Finish.
-
Studio creates a new project with the specified Jmix template and starts the migration process. You will see a notification about it in the bottom right corner of the IDE.
When the migration is finished, Studio creates the
MigrationResult.md
file and opens it in the editor window. The file describes what has been done automatically and recommendations on what should be done manually. -
Add required dependencies to the
build.gradle
file. The migration procedure adds only the known Jmix counterparts of the CUBA add-ons. -
Your next goal is to compile the project. Click Build → Build Project in the IDE main menu.
See compilation errors in the build output panel and fix your code to comply with the new API. Use the information from the Changed API section above.
-
After successful compilation, check the main database connection in the Data Stores section of the Jmix tool window.
Jmix Studio will modify the database schema and run some updates automatically. Never use production databases at the development stage! -
To update an existing CUBA database to be compatible with the new Jmix application, do the following:
-
Ensure that
application.properties
file contains the line:jmix.liquibase.contexts = cuba
-
Click Update in the context menu of the Main Data Store item. Studio will run Liquibase changelogs that come with
jmix-cuba
module. If the process is finished successfully, your database is compatible with Jmix modules included in the project.
-
-
Now you can run the application using the Jmix Application run/debug configuration.
By default, it first checks the database schema and generates a Liquibase changelog if it differs from the application data model. Review the generated changelog carefully and remove from it all potentially dangerous instructions like
drop
andalter
. You can use Remove and Ignore command in the Changelog Preview window to remove a selected instruction. Then your choice will be remembered in thejmix-studio.xml
file of your project, and when you run the application next time, the ignored instructions will not be generated. -
To create a new empty database for your application, do the following:
-
Change Liquibase context in
application.properties
:jmix.liquibase.contexts = migrated
-
Replace all appearances of the users table name in
resources/<base-package>/liquibase/changelog/010-init-user.xml
toSEC_USER
. For example:<createTable tableName="APP_USER">
→<createTable tableName="SEC_USER">
, etc. -
Click Recreate in the context menu of the Main Data Store item. Studio will drop/create the database and run Liquibase changelogs from all Jmix modules.
-
Run the application using the Jmix Application run/debug configuration. Studio will generate a Liquibase changelog for entities in your data model. Alternatively, you can create a changelog file manually and add all SQL statements from the CUBA project
create-db.sql
files using Liquibasesql
instructions.
-
File Storage
Local file storage structure in Jmix is the same as in CUBA. You can just move all files from the work/filestorage
directory of your CUBA application to the Jmix file storage directory which is {user.dir}/.jmix/work/filestorage
by default and can be changed by the jmix.localfs.storageDir
property.
Make sure that in screen descriptors, upload fields working with FileDescriptor
attributes are defined as cuba:cubaUpload
.
WebDAV
This section describes how to migrate code and data related to the WebDAV add-on.
-
Add the premium repository and add-on dependencies to your
build.gradle
:repositories { // ... maven { url = 'https://global.repo.jmix.io/repository/premium' credentials { username = rootProject['premiumRepoUser'] password = rootProject['premiumRepoPass'] } } } dependencies { implementation 'io.jmix.webdav:jmix-webdav-starter' implementation 'io.jmix.webdav:jmix-webdav-ui-starter' implementation 'io.jmix.webdav:jmix-webdav-rest-starter' // ...
Refresh the project using Load Gradle Changes popup at the top right corner of the edit window or using Reload All Gradle Projects action of the Gradle tool window.
-
Replace CUBA WebDAV packages with Jmix ones throughout your codebase:
-
com.haulmont.webdav.entity.
→io.jmix.webdav.entity.
-
com.haulmont.webdav.annotation.
→io.jmix.webdav.annotation.
-
com.haulmont.webdav.components.
→io.jmix.webdavui.component.
-
-
Fix WebDAV UI components declaration in your screen XML descriptors.
-
Replace
webdav
schema URI :xmlns:webdav="http://schemas.haulmont.com/webdav/ui-component.xsd
→xmlns:webdav="http://jmix.io/schema/webdav/ui
-
Replace component XML elements:
-
document-link
→documentLink
-
document-version-link
→documentVersionLink
-
webdav-document-upload
→webdavDocumentUpload
-
-
-
Jmix WebDAV add-on works only with attributes of
WebdavDocument
type, so if you haveFileDescriptor
attributes annotated with@WebdavSupport
, you should change the attribute type and migrate data stored in the corresponding column. Let’s consider this process on an example.Suppose you have the following entity with a
FileDescriptor
attribute supporting WebDAV:@JmixEntity @Table(name = "DEMO_DOC") @Entity(name = "demo_Doc") public class Doc extends StandardEntity { @WebdavSupport @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "FILE_ID") private FileDescriptor file;
First, replace
FileDescriptor
type withWebdavDocument
:@JmixEntity @Table(name = "DEMO_DOC") @Entity(name = "demo_Doc") public class Doc extends StandardEntity { @WebdavSupport @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "FILE_ID") private WebdavDocument file;
@WebdavSupport
annotation is not required in this case, but it can be used to disable versioning.If there are
WebdavDocumentLink
components created for this attribute, replacewithFileDescriptor()
invocations withwithWebdavDocument()
.Next you need to create a Liquibase changelog updating data of the
FILE_ID
column. Create an XML file (choose an appropriate name, for example020-migrate-webdav.xml
) in thesrc/main/resources/<base-package>/liquibase/changelog
directory with the following content:<?xml version="1.0" encoding="UTF-8"?> <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd" context="cuba"> <changeSet id="1" author="demo"> <dropForeignKeyConstraint baseTableName="DEMO_DOC" constraintName="FK_DEMO_DOC_ON_FILE"/> <update tableName="DEMO_DOC"> <column name="FILE_ID" valueComputed="(select wd.id from webdav_webdav_document_version wdv, webdav_webdav_document wd where wdv.file_descriptor_id = FILE_ID and wdv.webdav_document_id = wd.id)"/> </update> <addForeignKeyConstraint baseColumnNames="FILE_ID" baseTableName="DEMO_DOC" constraintName="FK_DEMO_DOC_ON_FILE" referencedColumnNames="ID" referencedTableName="WEBDAV_WEBDAV_DOCUMENT"/> </changeSet> </databaseChangeLog>
In general, you should create such changelogs for each
FileDescriptor
attribute which you have turned intoWebdavDocument
. The changelogs should match the following pattern:<changeSet id="{NUM}" author="sample"> <dropForeignKeyConstraint baseTableName="{ENTITY_TABLE_NAME}" constraintName="{FK_FOR_DOCUMENT}"/> <update tableName="{ENTITY_TABLE_NAME}"> <column name="{DOCUMENT_COLUMN_NAME}" valueComputed="(select wd.id from webdav_webdav_document_version wdv, webdav_webdav_document wd where wdv.file_descriptor_id = {DOCUMENT_COLUMN_NAME} and wdv.webdav_document_id = wd.id)"/> </update> <addForeignKeyConstraint baseColumnNames="{DOCUMENT_COLUMN_NAME}" baseTableName="{ENTITY_TABLE_NAME}" constraintName="{FK_FOR_DOCUMENT}" referencedColumnNames="ID" referencedTableName="WEBDAV_WEBDAV_DOCUMENT"/> </changeSet>
where
-
{NUM}
- number of the changelog in the file. -
{ENTITY_TABLE_NAME}
- entity table name. -
{FK_FOR_DOCUMENT}
- foreign key for referencedFileDescriptor
. -
{DOCUMENT_COLUMN_NAME}
- name of theFileDescriptor
column.
Click Update in the context menu of the Main Data Store item. Studio will run existing Liquibase changelogs.
When you start the application, Studio will generate Liquibase changelogs for the difference between the database schema and your data model. Remove the instruction to drop the
FILE_DESCRIPTOR_ID.WEBDAV_WEBDAV_DOCUMENT_VERSION
column from the changelog (use Remove and Ignore command in the Changelog Preview window):<dropColumn columnName="FILE_DESCRIPTOR_ID" tableName="WEBDAV_WEBDAV_DOCUMENT_VERSION"/>
Keep this column until you complete the migration.
Start the application, go to Administration → JMX Console and open the
jmix.cuba:type=MigrationHelper
MBean. Execute theconvertCubaFileDescriptorsForWebdav()
operation. -
-
Set up HTTPS for your application. See the Configuring HTTPS guide for how to do it with a self-signed certificate.
-
Move your local file storage content as described above.
Frontend
If your project has a frontend module created with CUBA React client, you can migrate it to Jmix as follows:
-
Copy
public
,src
directories and all files from the root ofmodules/front
directory of your CUBA project intofront
directory of your new Jmix project. -
See Jmix Frontend UI → Migration from CUBA guide for further instructions.