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
@JmixEntityannotation. -
Custom datatype classes are annotated with
@DatatypeDefwhich 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 injection 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
DataManagerlevel. 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
SetupAttributeAccessHandlerandSetupAttributeAccessHandler. -
Screen component permissions.
-
Session attributes defined in the Access Groups.
-
ClusterManagerAPIinterface and its implementation. -
Editor screen opening history and
@TrackEditScreenHistoryannotation. -
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
StandardTenantEntityto 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.StandardTenantEntityto the import of your ownStandardTenantEntity. -
In the
Userentity, implement theAcceptsTenantinterface and add thetenantattribute annotated with@TenantIdand mapped to theSYS_TENANT_IDcolumn: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
tenantattribute to the user browse and edit screens as described in items 3, 4, 5 of the Multitenancy / Configuring Users section. -
Rename
CUBASDBMT_TENANTtable toMTEN_TENANTusing the following Liquibase changeset (it’s needed only in Jmix 1.1.0, becausejmix-cubamodule 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.mdfile 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.gradlefile. The migration procedure adds only the known Jmix counterparts of the CUBA add-ons. -
Configure the user traits. CUBA users always contain the
Audit of creation,Audit of modification, andSoft Deletetraits. For Jmix users these traits are optional and disabled by default.-
If you need audit and soft deletion for Jmix users, follow the steps below:
-
Enable traits in the Studio’s Entity Designer.
-
Update column names of the corresponding properties:
-
createdDate:CREATED_DATE→CREATE_TS -
lastModifiedBy:LAST_MODIFIED_BY→UPDATED_BY -
lastModifiedDate:LAST_MODIFIED_DATE→UPDATE_TS -
deletedDate:DELETED_DATE→DELETE_TS
-
-
-
If the
Audit of creationorAudit of modificationtrait is no longer needed, just don’t enable the trait, and related columns will be removed in the further changelog. -
If you don’t use the
Soft Deletetrait, it may cause restoration of all previously deleted users. So you need to remove all records with a not null value of theDELETE_TScolumn from theSEC_USERtable. After that, with the disabledSoft Deletetrait, related columns will be removed in the further changelog.
-
-
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.propertiesfile contains the line:main.liquibase.contexts = cuba -
Click Update in the context menu of the Main Data Store item. Studio will run Liquibase changelogs that come with
jmix-cubamodule. If the process is finished successfully, your database is compatible with Jmix modules included in the project.
-
-
If the
cubamodule is included, it contains some screens using legacy theme properties. In this case, if the project contains custom theme, make sure its<custom-theme-name>.propertiesfile includes base theme properties from thecubamodule, not Jmix:@include=io/jmix/ui/theme/helium-theme.propertiesshould be changed to
@include=com/haulmont/cuba/helium-theme.properties -
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
dropandalter.-
Changes related to the
SEC_REMEMBER_ME,SEC_SCREEN_HISTORY, andSEC_SEARCH_FOLDERtables are safe to apply (but still can be ignored). -
It’s recommended not to drop columns of the
SEC_USERtable till the very end of migration.
You can use Remove and Ignore command in the Changelog Preview window to remove a selected instruction. Then your choice will be remembered in the
jmix-studio.xmlfile 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:main.liquibase.contexts = migrated -
Replace all appearances of the users table name in
resources/<base-package>/liquibase/changelog/010-init-user.xmltoSEC_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.sqlfiles using Liquibasesqlinstructions.
-
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 in 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
webdavschema 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
WebdavDocumenttype, so if you haveFileDescriptorattributes 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
FileDescriptorattribute 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
FileDescriptortype 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;@WebdavSupportannotation is not required in this case, but it can be used to disable versioning.If there are
WebdavDocumentLinkcomponents created for this attribute, replacewithFileDescriptor()invocations withwithWebdavDocument().Next you need to create a Liquibase changelog updating data of the
FILE_IDcolumn. Create an XML file (choose an appropriate name, for example020-migrate-webdav.xml) in thesrc/main/resources/<base-package>/liquibase/changelogdirectory 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
FileDescriptorattribute 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 theFileDescriptorcolumn.
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_VERSIONcolumn 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=MigrationHelperMBean. 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,srcdirectories and all files from the root ofmodules/frontdirectory of your CUBA project intofrontdirectory of your new Jmix project. -
See Jmix Frontend UI → Migration from CUBA guide for further instructions.