Migration from CUBA

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.

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.

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 and SetupAttributeAccessHandler.

  • Screen component permissions.

  • Session attributes defined in the Access Groups.

  • ClusterManagerAPI interface and its implementation.

  • Editor screen opening history and @TrackEditScreenHistory annotation.

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.AccessGroupio.jmix.security.role.annotation.RowLevelRole

  • com.haulmont.cuba.security.app.group.annotation.JpqlConstraintio.jmix.security.role.annotation.JpqlRowLevelPolicy

  • com.haulmont.cuba.security.app.group.annotation.Constraintio.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.Roleio.jmix.securitydata.entity.ResourceRoleEntity

  • com.haulmont.cuba.security.entity.Groupio.jmix.securitydata.entity.RowLevelRoleEntity

  • com.haulmont.cuba.security.entity.UserRoleio.jmix.securitydata.entity.RoleAssignmentEntity

  • com.haulmont.cuba.security.entity.Permissionio.jmix.securitydata.entity.ResourcePolicyEntity

  • com.haulmont.cuba.security.entity.Constraintio.jmix.securitydata.entity.RowLevelPolicyEntity

Multitenancy

After running the automatic migration procedure, follow the steps below.

  1. 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 of com.haulmont.addon.sdbmt.entity.StandardTenantEntity to the import of your own StandardTenantEntity.

  2. In the User entity, implement the AcceptsTenant interface and add the tenant attribute annotated with @TenantId and mapped to the SYS_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;
        }
    }
  3. Add tenant attribute to the user browse and edit screens as described in items 3, 4, 5 of the Multitenancy / Configuring Users section.

  4. Rename CUBASDBMT_TENANT table to MTEN_TENANT using the following Liquibase changeset (it’s needed only in Jmix 1.1.0, because jmix-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.ReportGuiManagerio.jmix.reports.runner.ReportRunner

Entity snapshots

  • com.haulmont.cuba.core.app.EntitySnapshotServiceio.jmix.audit.snapshot.EntitySnapshotManager

  • com.haulmont.cuba.gui.app.core.entitydiff.EntityDiffViewerio.jmix.auditui.screen.snapshot.SnapshotDiffViewer

  • <frame id="diffFrame" src="/com/haulmont/cuba/gui/app/core/entitydiff/diff-view.xml"/><fragment id="diffFrame" screen="snapshotDiff"/>

Email sending

  • com.haulmont.cuba.core.app.EmailServiceio.jmix.email.Emailer

  • com.haulmont.cuba.core.global.EmailInfoBuilder#setCaptionio.jmix.email.EmailInfoBuilder#setSubject

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:

  • Projects using HSQLDB as a main data store may have an invalid connection string. We recommend switching your project to a different database before migration.

  • Test classes are not copied to the Jmix project.

Follow the steps below to run the automatic migration procedure.

  1. Open your CUBA project in Jmix Studio.

  2. Wait until the project is imported and fully indexed. Watch the IDE progress bar and wait until it stops displaying new messages.

  3. 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.

  4. Studio starts the New Jmix project wizard.

  5. Select the latest Jmix version (at least 1.1.0) and the project JDK used in your CUBA project. Click Next.

  6. On the next step of the wizard, enter the new Jmix project name and location. Click Finish.

  7. 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.

  8. Add required dependencies to the build.gradle file. The migration procedure adds only the known Jmix counterparts of the CUBA add-ons.

  9. 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.

  10. 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!
  11. To update an existing CUBA database to be compatible with the new Jmix application, do the following:

    1. Ensure that application.properties file contains the line:

      jmix.liquibase.contexts = cuba
    2. 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.

  12. 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 and alter. 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.xml file of your project, and when you run the application next time, the ignored instructions will not be generated.

  13. To create a new empty database for your application, do the following:

    1. Change Liquibase context in application.properties:

      jmix.liquibase.contexts = migrated
    2. Replace all appearances of the users table name in resources/<base-package>/liquibase/changelog/010-init-user.xml to SEC_USER. For example: <createTable tableName="APP_USER"><createTable tableName="SEC_USER">, etc.

    3. 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.

    4. 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 Liquibase sql instructions.

  14. If your project has a frontend module created with CUBA React client, you can migrate it to Jmix as follows:

    1. Copy public, src directories and all files from the root of modules/front directory of your CUBA project into front directory of your new Jmix project.

    2. See Jmix Frontend UI → Migration from CUBA guide for further instructions.