1. Overview

The add-on provides a universal REST API with the following functionality:

  • CRUD operations on entities.

  • Execution of predefined JPQL queries.

  • Execution of service methods.

  • Getting metadata (entities, views, enumerations, datatypes).

  • Getting current user permissions (access to entities, attributes, specific permissions).

  • Getting current user information (name, language, time zone, etc.).

  • Uploading and downloading files.

REST API uses the OAuth2 protocol for authentication and supports anonymous access.

All REST API endpoints work with data according to the authenticated user permissions set in security subsystem.

Tip

The detailed documentation on the API endpoints is published at http://files.cuba-platform.com/swagger/7.1.

2. Getting Started

Installation
  • If you are using Studio 11+, open the CUBA > Marketplace main menu item, then find and install the REST API add-on.

  • Otherwise, see Using Public Add-ons documentation for how to install the add-on using its coordinates in the repository. The coordinates can be copied from the add-on page at Marketplace.

Keep in mind that the add-on is built only for the framework version 7.1 and above. In the earlier versions, REST API was included in the core framework.

Testing basic functionality
  • In Studio, you should see the REST element in the CUBA project tree after Gradle refresh is completed.

  • Start the application and test the REST API using curl command line tool:

    1. Request OAuth token:

      curl -X POST \
        http://localhost:8080/app/rest/v2/oauth/token \
        -H 'Authorization: Basic Y2xpZW50OnNlY3JldA==' \
        -H 'Content-Type: application/x-www-form-urlencoded' \
        -d 'grant_type=password&username=admin&password=admin'

      You will get a JSON response with some access_token value. Use it for further requests in the Authorization header.

    2. Request the list of roles (replace <access_token> with the value obtained on the previous step):

        curl -X GET \
        'http://localhost:8080/app/rest/v2/entities/sec$Role' \
        -H 'Authorization: Bearer <access_token>'

      The response will contain all registered roles, provided that the user for which the token was obtained has permissions to read the sec$Role entity.

3. Features

This section covers the REST API features and configuration options. For practical usage examples, see the next section.

3.1. Predefined JPQL Queries Configuration

In the CUBA application, predefined JPQL queries must be specified in files registered in the cuba.rest.queriesConfig application property of the web or portal module (e.g in the web-app.properties file):

cuba.rest.queriesConfig = +com/company/myapp/rest-queries.xml

The rest-queries.xml file must be placed in the root package of the web or portal module (e.g. com.company.myapp). Its content is defined by the rest-queries.xsd schema, for example:

<?xml version="1.0"?>
<queries xmlns="http://schemas.haulmont.com/cuba/rest-queries.xsd">
    <query name="carByVin" entity="sample$Car" view="carEdit">
        <jpql><![CDATA[select c from sample$Car c where c.vin = :vin]]></jpql>
        <params>
            <param name="vin" type="java.lang.String"/>
        </params>
    </query>
    <query name="allColours" entity="sample$Colour" view="_local">
        <jpql><![CDATA[select u from sample$Colour u order by u.name]]></jpql>
    </query>
    <query name="carsByIds" entity="sample$Car" view="carEdit" cacheable="true">
        <jpql><![CDATA[select c from sample$Car c where c.id in :ids]]></jpql>
        <params>
            <param name="ids" type="java.util.UUID[]"/>
        </params>
    </query>
    <query name="myOrders" entity="sample$Order" view="orderBrowse">
        <jpql><![CDATA[select o from sample$Order o where o.createdBy = :session$userLogin]]></jpql>
    </query>
</queries>

An example of how to configure and execute a query can be found in the Executing a JPQL Query (GET) and Executing a JPQL Query (POST) chapter.

The platform also provides the predefined all query for getting all instances of a specified entity type. It can be used with /count to receive the total number of entity instances, for example:

http://localhost:8080/app/rest/v2/queries/sales$Order/all/count

The query element can have the cacheable attribute that enables caching of the query.

A query can contain predefined parameters that take the values of the current user id and login: session$userId and session$userLogin. You don’t have to declare them in the params element (see the example above).

3.2. Services Configuration

The list of service methods that are available via the REST API must be configured in the CUBA application in files registered in the cuba.rest.servicesConfig application property of the web or portal module (e.g in the web-app.properties file):

cuba.rest.servicesConfig = +com/company/myapp/rest-services.xml

The content of the rest-services.xml must be placed in the root package of the web or portal module (e.g. com.company.myapp). Its content is defined by the rest-services-v2.xsd schema, for example:

<?xml version="1.0" encoding="UTF-8"?>
<services xmlns="http://schemas.haulmont.com/cuba/rest-services-v2.xsd">
    <service name="myapp_SomeService">
        <method name="sum">
            <param name="number1"/>
            <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.

An example of how to configure and invoke a service can be found in the Service Method Invocation (GET) chapter.

If some service method needs to be invoked without authentication even when the anonymous access to the REST API is disabled, then this method may be marked with the anonymousAllowed="true" attribute in the services configuration file:

<?xml version="1.0" encoding="UTF-8"?>
<services xmlns="http://schemas.haulmont.com/cuba/rest-services-v2.xsd">
    <service name="myapp_SomeService">
        <method name="sum" anonymousAllowed="true">
            <param name="number1"/>
            <param name="number2"/>
        </method>
    </service>
</services>

3.3. Data Model Versioning

REST API can handle data model changes. It is useful when, for example, some entity attribute was renamed, but REST API client doesn’t know about this modification and expects the attribute to have an old name.

For such cases REST API allows you to define transformation rules for entities JSON. If the client application sends the data model version in the request query parameter then the JSON in REST API method response or request body will be transformed according to transformation rules defined for that particular domain model version.

JSON transformation rules must be specified in files registered in cuba.rest.jsonTransformationConfig application property of the web or portal module (e.g in the web-app.properties file):

cuba.rest.jsonTransformationConfig = +com/company/myapp/rest-json-transformations.xml

The rest-json-transformations.xml file must be placed in the web or portal module (e.g. in package com.company.myapp). Its content is defined by the {xsd_url}/rest-json-transformations.xsd[rest-json-transformations.xsd] schema. File example:

<?xml version="1.0"?>
<transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">

    <transformation modelVersion="1.0" oldEntityName="sales$OldOrder" currentEntityName="sales$NewOrder">
        <renameAttribute oldName="oldNumber" currentName="number"/>
        <renameAttribute oldName="date" currentName="deliveryDate"/>
        <toVersion>
            <removeAttribute name="discount"/>
        </toVersion>
    </transformation>

    <transformation modelVersion="1.0" currentEntityName="sales$Contractor">
        <renameAttribute oldName="summary" currentName="total"/>
        <renameAttribute oldName="familyName" currentName="lastName"/>
        <fromVersion>
            <removeAttribute name="city"/>
            <removeAttribute name="country"/>
        </fromVersion>
        <toVersion>
            <removeAttribute name="phone"/>
        </toVersion>
    </transformation>

    <transformation modelVersion="1.1" currentEntityName="sales$NewOrder">
        <renameAttribute oldName="date" currentName="deliveryDate"/>
    </transformation>

</transformations>

Standard transformers configured in the config file can perform the following transformations of entity JSON:

  • rename entity

  • rename entity attribute

  • remove entity attribute

JSON transformation works for the following REST API endpoints:

  • /entities - getting entities list, getting a single entity, entity create, entity update, entity delete

  • /queries - entities JSON returned by the query will be transformed

  • /services - JSON transformations will be applied both to entities returned by the service method and to entities passed as a service method argument

JSON transformations are applied if the request to the REST API contains the modelVersion URL parameter with the data model version number.

See the Data Model Versioning Example to understand how to configure data model versioning and use it from the client application.

3.4. CORS Settings

By default, all CORS requests to the REST API are allowed. To restrict the origins list you can define the cuba.rest.allowedOrigins application property.

3.5. Anonymous Access

By default, anonymous access is disabled. To enable it, use the cuba.rest.anonymousEnabled application property. A request is considered to be anonymous if it doesn’t contain an Authentication header. In this case, the SecurityContext will contain an anonymous user session.

To set up permissions for anonymous user you must define roles for the user specified by the cuba.anonymousLogin application property.

3.6. Other REST API Settings

cuba.rest.client.id - defines a default REST API client id.

cuba.rest.client.secret - defines a default REST API client secret.

cuba.rest.client.tokenExpirationTimeSec - defines an access token expiration time for the default client in seconds.

cuba.rest.client.refreshTokenExpirationTimeSec - defines a refresh token expiration time for the default client in seconds.

cuba.rest.client.authorizedGrantTypes - a list of authorized grant types for the default client. To disable refresh tokens remove the refresh_token item from the property value.

cuba.rest.maxUploadSize - defines a maximum file size that can be uploaded with the REST API.

cuba.rest.reuseRefreshToken - specifies whether a refresh token should be reused.

cuba.rest.requiresSecurityToken - indicates that additional system attribute must be sent in JSON. See details in Security Constraints for Collection Attributes.

cuba.rest.tokenMaskingEnabled - specifies whether REST API token values should be masked in application logs.

3.7. Creating Custom OAuth2 Protected Controllers

If you need to create a custom REST controller protected with the OAuth2 authentication then you have to do the following:

  1. Suppose you have the following REST controller:

    package com.company.test.portal.myapi;
    
    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")
    public class MyController {
    
        @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.2.xsd">
    
        <!-- Define a base package for your controllers-->
        <context:component-scan base-package="com.company.test.portal.myapi"/>
    
        <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>
  3. Define an additive application property cuba.restSpringContextConfig in the properties file of the module,.e.g. portal-app.properties:

    cuba.restSpringContextConfig = +com/company/test/rest-dispatcher-spring.xml
  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.

    Warning

    URL of the custom controller MUST NOT start with the /rest/v2.

3.8. Security Constraints for Collection Attributes

Let’s consider the following situation:

  • Your data model contains Order and OrderLine entities which form the one-to-many composition.

  • Your REST client loads an instance of Order together with the nested collection of OrderLine instances.

  • There are security constraints that filter out some OrderLine instances, so the client does not load them and doesn’t know they exist. Say line5 is not loaded by the client but exists in the database.

  • If your client removes, say, line2 from the collection and then saves the whole composition using the /entities/{entityName}/{entityId} endpoint, there are two outcomes:

    1. If the constraints were not changed since the entities were loaded, the framework restores the filtered line5 instance in the collection and deletes only line2, which is the correct behavior.

    2. If the constraints were changed in a way that line5 is now available to the client, the framework cannot restore the information about filtered collection elements correctly. As a result, both line2 and line5 will be deleted.

If you are concerned with the case described above, you can eliminate possible data loss by sending a special system attribute in the JSON representing your entities. This attribute is called __securityToken and automatically included in resulting JSON if the cuba.rest.requiresSecurityToken application property is set to true. The responsibility of your REST client is to return this attribute back when saving entities.

An example of entity JSON including security token:

{
  "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=="
}

The __securityToken attribute contains encoded identifiers of filtered instances, so the framework can always restore the required information regardless of changes in constraints.

3.9. Persistent Token Store

By default, OAuth tokens are stored in memory only. If you want to persist them in the database as well, you should set the cuba.rest.storeTokensInDb application property to true. The property value is stored in the database, so you can change its value in the Administration > Application Properties screen.

Expired tokens in the database store must be periodically deleted. The cron expression for scheduled removing of expired tokens is specified in the cuba.rest.deleteExpiredTokensCron application property.

3.10. Project-specific Swagger Documentation

The generic documentation on the REST API is available at http://files.cuba-platform.com/swagger/7.1.

Any running CUBA application also exports the project-specific documentation generated according to the Swagger specification version 2.0.

The documentation is available at the following URLs:

  • /rest/v2/docs/swagger.yaml - YAML version of generic documentation.

  • /rest/v2/docs/swagger.json - JSON version of generic documentation.

  • /rest/v2/docs/swaggerDetailed.yaml - YAML version of project-specific Swagger documentation.

  • /rest/v2/docs/swaggerDetailed.json - JSON version of project-specific Swagger documentation.

For example:

http://localhost:8080/app/rest/v2/docs/swagger.yaml
http://localhost:8080/app/rest/v2/docs/swaggerDetailed.yaml

The documentation can be used to visualize, test or generate a client code for the REST API. See the following tools: Swagger UI, Swagger Inspector, Postman, Swagger Codegen.

The generated documentation includes:

  1. CRUD operations, such as:

    • entity CRUD operations:

      swagger crud
    • ,

    • filtering entities.

    All CRUD parameters and responses have a model available, for example:

    swagger crud model
  2. Predefined REST queries:

    swagger query
  3. Exposed services:

    swagger service

4. Using REST API

This section contains REST API usage examples.

Tip

See the detailed documentation on the API endpoints at http://files.cuba-platform.com/swagger/7.1.

4.1. Getting an OAuth Token

An OAuth token is required for any REST API method (except when you are using an anonymous access). A token can be obtained by the POST request on the address:

http://localhost:8080/app/rest/v2/oauth/token

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.

The request type must be application/x-www-form-urlencoded, the encoding is UTF-8.

The request must contain the following parameters:

  • grant_type - password.

  • username - application user login.

  • password - application user password.

POST /oauth/token
Authorization: Basic Y2xpZW50OnNlY3JldA==
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=smith&password=qwerty123

You can also use cURL:

curl -H "Content-type: application/x-www-form-urlencoded" -H "Authorization: Basic Y2xpZW50OnNlY3JldA==" -d "grant_type=password&username=admin&password=admin" http://localhost:8080/app/rest/v2/oauth/token

Method returns a JSON object:

{
  "access_token": "29bc6b45-83cd-4050-8c7a-2a8a60adf251",
  "token_type": "bearer",
  "refresh_token": "e765446f-d49e-4634-a6d3-2d0583a0e7ea",
  "expires_in": 43198,
  "scope": "rest-api"
}

An access token value is in the access_token property.

In order to use the access token, put it in the Authorization header with the Bearer type, for example:

Authorization: Bearer 29bc6b45-83cd-4050-8c7a-2a8a60adf251

The refresh_token property contains a refresh token value. A refresh token cannot be used for accessing the protected resources, but it has a longer lifetime than an access token and it can be used to obtain new access token when the current one is expired.

The request for getting new access token using the refresh token must contain the following parameters:

  • grant_type - refresh_token.

  • refresh_token - a refresh token value.

POST /oauth/token
Authorization: Basic Y2xpZW50OnNlY3JldA==
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=e765446f-d49e-4634-a6d3-2d0583a0e7ea

See also the following application properties related to tokens:

4.2. REST API Authentication with LDAP

LDAP Authentication for REST can be enabled using the following properties:

  • cuba.rest.ldap.enabled - whether LDAP authentication is enabled or not.

  • cuba.rest.ldap.urls – LDAP server URL.

  • cuba.rest.ldap.base – base DN for user search.

  • cuba.rest.ldap.user – the distinguished name of a system user which has the right to read the information from the directory.

  • cuba.rest.ldap.password – the password for the system user defined in the cuba.web.ldap.user property.

  • cuba.rest.ldap.userLoginField - the name of an LDAP user attribute that is used for matching the login name. sAMAccountName by default (suitable for Active Directory).

Example of local.app.properties file:

cuba.rest.ldap.enabled = true
cuba.rest.ldap.urls = ldap://192.168.1.1:389
cuba.rest.ldap.base = ou=Employees,dc=mycompany,dc=com
cuba.rest.ldap.user = cn=System User,ou=Employees,dc=mycompany,dc=com
cuba.rest.ldap.password = system_user_password

You can obtain OAuth token using the following end-point:

http://localhost:8080/app/rest/v2/ldap/token

An access to this endpoint is protected with the basic authentication. REST API client identifier and password are used for basic authentication. Please note that these are not the 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.

Request parameters are the same as for standard authentication:

  • grant_type - always password.

  • username - application user login.

  • password - application user password.

The request type must be application/x-www-form-urlencoded, the encoding is UTF-8.

Also, standard authentication with login and password can be disabled:

cuba.rest.standardAuthenticationEnabled = false

4.3. Custom Authentication

Authentication mechanisms can provide access tokens by key, link, LDAP login and password, etc. REST API uses its own authentication mechanism that cannot be modified. In order to use custom authentication process, you need to create a REST controller and use its URL.

Let’s consider the custom authentication mechanism that enables getting an OAuth token by a promo code. In the following example we will use a sample application that contains the Coupon entity with code attribute. We will send this attribute’s value as an authentication parameter in GET request.

  1. Create a Coupon entity with the code attribute:

    @Column(name = "CODE", unique = true, length = 4)
    protected String code;
  2. Create a user with promo-user login on behalf of which the authentication will be performed.

  3. Create a new Spring configuration file with name rest-dispatcher-spring.xml under the root package (com.company.demo) of web module. The content of the file must be as follows:

    <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"
           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">
    
        <context:component-scan base-package="com.company.demo.web.rest"/>
    
    </beans>
  4. Include the file into the cuba.restSpringContextConfig application property in the modules/web/src/web-app.properties file:

    cuba.restSpringContextConfig = +com/company/demo/rest-dispatcher-spring.xml
  5. Create the rest package under the root package of web module and implement the custom Spring MVC controller in it. Use the OAuthTokenIssuer bean to generate and issue the REST API token for a user after the custom authentication:

    @RestController
    @RequestMapping("auth-code")
    public class AuthCodeController {
        @Inject
        private OAuthTokenIssuer oAuthTokenIssuer;
        @Inject
        private LoginService loginService;
        @Inject
        private Configuration configuration;
        @Inject
        private DataManager dataManager;
        @Inject
        private MessageTools messageTools;
    
        // here we check secret code and issue token using OAuthTokenIssuer
        @RequestMapping(method = RequestMethod.GET)
        public ResponseEntity get(@RequestParam("code") String authCode) {
            // obtain system session to be able to call middleware services
            WebAuthConfig webAuthConfig = configuration.getConfig(WebAuthConfig.class);
            UserSession systemSession;
            try {
                systemSession = loginService.getSystemSession(webAuthConfig.getTrustedClientPassword());
            } catch (LoginException e) {
                throw new RuntimeException("Error during system auth");
            }
    
            // set security context
            AppContext.setSecurityContext(new SecurityContext(systemSession));
            try {
                // find coupon with code
                LoadContext<Coupon> loadContext = LoadContext.create(Coupon.class)
                        .setQuery(LoadContext.createQuery("select c from demo$Coupon c where c.code = :code")
                                .setParameter("code", authCode));
    
                if (dataManager.load(loadContext) == null) {
                    // if coupon is not found - code is incorrect
                    return new ResponseEntity<>(new ErrorInfo("invalid_grant", "Bad credentials"), HttpStatus.BAD_REQUEST);
                }
    
                // generate token for "promo-user"
                OAuthTokenIssuer.OAuth2AccessTokenResult tokenResult =
                        oAuthTokenIssuer.issueToken("promo-user", messageTools.getDefaultLocale(), Collections.emptyMap());
                OAuth2AccessToken accessToken = tokenResult.getAccessToken();
    
                // set security HTTP headers to prevent browser caching of security token
                HttpHeaders headers = new HttpHeaders();
                headers.set(HttpHeaders.CACHE_CONTROL, "no-store");
                headers.set(HttpHeaders.PRAGMA, "no-cache");
                return new ResponseEntity<>(accessToken, headers, HttpStatus.OK);
            } finally {
                // clean up security context
                AppContext.setSecurityContext(null);
            }
        }
    
        // POJO for JSON error messages
        public static class ErrorInfo implements Serializable {
            private String error;
            private String error_description;
    
            public ErrorInfo(String error, String error_description) {
                this.error = error;
                this.error_description = error_description;
            }
    
            public String getError() {
                return error;
            }
    
            public String getError_description() {
                return error_description;
            }
        }
    }
  6. Exclude the rest package from scanning in web/core modules: the OAuthTokenIssuer bean is available only in REST API context, and scanning for it in the application context will cause an error.

    <context:component-scan base-package="com.company.demo">
        <context:exclude-filter type="regex" expression="com\.company\.demo\.web\.rest\..*"/>
    </context:component-scan>
  7. Now users will be able to obtain OAuth2 access code using GET HTTP request with the code parameter to

    http://localhost:8080/app/rest/auth-code?code=A325

    The result will be:

    {"access_token":"74202587-6c2b-4d74-bcf2-0d687ea85dca","token_type":"bearer","expires_in":43199,"scope":"rest-api"}

    The obtained access token should then be passed to REST API, as described in the documentation.

4.3.1. Social Login in REST API

The mechanism of social login can be used in REST API too. The complete sample application is available on GitHub and described in the [social_login] section, below are the key points of getting an access token with a Facebook account.

  1. Create the restapi package under the root package of web module and implement the custom Spring MVC controller in it. This controller should contain two main methods: get() to get a ResponseEntity instance and login() to obtain an OAuth token.

    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity get() {
        String loginUrl = getAsPrivilegedUser(() ->
                facebookService.getLoginUrl(getAppUrl(), OAuth2ResponseType.CODE_TOKEN)
        );
    
        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.LOCATION, loginUrl);
        return new ResponseEntity<>(headers, HttpStatus.FOUND);
    }

    Here we check the Facebook code, obtain an access code and issue the access token using OAuthTokenIssuer:

    @RequestMapping(method = RequestMethod.POST, value = "login")
    public ResponseEntity<OAuth2AccessToken> login(@RequestParam("code") String code) {
        User user = getAsPrivilegedUser(() -> {
            FacebookUserData userData = facebookService.getUserData(getAppUrl(), code);
    
            return socialRegistrationService.findOrRegisterUser(
                userData.getId(), userData.getEmail(), userData.getName());
        });
    
        OAuth2AccessTokenResult tokenResult = oAuthTokenIssuer.issueToken(user.getLogin(),
                messageTools.getDefaultLocale(), Collections.emptyMap());
    
        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.CACHE_CONTROL, "no-store");
        headers.set(HttpHeaders.PRAGMA, "no-cache");
        return new ResponseEntity<>(tokenResult.getAccessToken(), headers, HttpStatus.OK);
    }
  2. Exclude the restapi package from scanning in web/core modules: the OAuthTokenIssuer bean is available only in REST API context, and scanning for it in the application context will cause an error.

    <context:component-scan base-package="com.company.demo">
        <context:exclude-filter type="regex" expression="com\.company\.demo\.restapi\..*"/>
    </context:component-scan>
  3. Create the facebook-login-demo.html file in the modules/web/web/VAADIN folder of your project. It will contain the JavaScript code running on HTML page:

    <html>
    <head>
        <title>Facebook login demo with REST-API</title>
        <script src="jquery-3.2.1.min.js"></script>
        <style type="text/css">
            #users {
                display: none;
            }
        </style>
    </head>
    <body>
    <h1>Facebook login demo with REST-API</h1>
    
    <script type="application/javascript"...>
    </script>
    
    <a id="fbLink" href="/app/rest/facebook">Login with Facebook</a>
    
    <div id="users">
        You are logged in!
    
        <h1>Users</h1>
    
        <div id="usersList">
        </div>
    </div>
    
    </body>
    </html>

    The following script will try to login with Facebook. Firstly, it will remove code parameters from URL, then it will pass the code to REST API to get an OAuth access token, and in case of successful authentication we will be able to load and save data as usual.

    var oauth2Token = null;
    
    function tryToLoginWithFacebook() {
        var urlHash = window.location.hash;
    
        if (urlHash && urlHash.indexOf('&code=') >= 0) {
            console.log("Try to login to CUBA REST-API!");
    
            var urlCode = urlHash.substring(urlHash.indexOf('&code=') + '&code='.length);
            console.log("Facebook code: " + urlCode);
    
            history.pushState("", document.title, window.location.pathname);
    
            $.post({
                url: '/app/rest/facebook/login',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                dataType: 'json',
                data: {code: urlCode},
                success: function (data) {
                    oauth2Token = data.access_token;
    
                    loadUsers();
                }
            })
        }
    }
    
    function loadUsers() {
        $.get({
            url: '/app/rest/v2/entities/sec$User?view=_local',
            headers: {
                'Authorization': 'Bearer ' + oauth2Token,
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            success: function (data) {
                $('#fbLink').hide();
                $('#users').show();
    
                $.each(data, function (i, user) {
                    $('#usersList').append("<li>" + user.name + " (" + user.email + ")</li>");
                });
            }
        });
    }
    
    tryToLoginWithFacebook();

    Another example or running a JavaScript code from CUBA applications you can find in the JavaScript Usage Example section.

4.4. Getting an Entity Instances List

Let’s suppose that the system has a sales$Order entity and we need to get a list of this entity instances. Besides, we need to get not all the records, but only 50 records, starting with the 100th one. A response must contain not only simple properties of the sales$Order entity but also an information about the order customer (a reference field named customer). Orders must be sorted by date.

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 put in the Authorization header with the Bearer type:

Authorization: Bearer 29bc6b45-83cd-4050-8c7a-2a8a60adf251

As a result, we get the following GET request URL:

http://localhost:8080/app/rest/v2/entities/sales$Order?view=order-edit-view&limit=50&offset=100&sort=date

Using cURL, the request will look like:

curl -H "Authorization: Bearer d335902c-9cb4-455e-bf92-24ca1d66d72f" http://localhost:8080/app/rest/v2/entities/sales$Order?view=order-edit&limit=50&offset=100&sort=date

The response will be like this:

[
  {
    "_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.

4.5. New Entity Instance Creation

New sales$Order entity instance can be created with the POST request on the address:

http://localhost:8080/app/rest/v2/entities/sales$Order

An OAuth token must be put in 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"
  }
}

Below is an example of cURL POST request that creates a new Order instance:

curl -H "Authorization: Bearer d335902c-9cb4-455e-bf92-24ca1d66d72f" -H "Content-Type: application/json" -X POST -d "{\"date\": \"2018-10-12 15:47:28\", \"amount\":  9.90, \"customer\": {\"id\": \"383ebce2-b295-7378-36a1-bcf93693821f\"}}" http://localhost:8080/app/rest/v2/entities/sales$Order

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.

First, let’s have a quick look to the Order class:

package com.company.sales.entity;

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;

    //getters and setters omitted
}

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"
  }
}

4.6. Existing Entity Instance Update

An existing sales$Order entity instance can be updated with the PUT request on the address:

http://localhost:8080/app/rest/v2/entities/sales$Order/5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50

The last part of the query here is the entity identifier.

An OAuth token must be put in 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"
  }
}

The response body will contain a modified entity:

{
  "_entityName": "sales$Order",
  "id": "5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50",
  "date": "2017-10-01",
  "updatedBy": "admin",
  "description": "Back to school",
  "version": 2,
  "number": "00017",
  "createdBy": "admin",
  "createTs": "2016-10-13 18:12:21.047",
  "updateTs": "2016-10-13 19:13:02.656",
  "customer": {
    "_entityName": "sales$Customer",
    "id": "5d111245-2ed0-abec-3bee-1a196da92e3e",
    "firstName": "Morgan",
    "lastName": "Collins",
    "createdBy": "admin",
    "createTs": "2016-10-13 15:31:27.821",
    "version": 1,
    "updateTs": "2016-10-13 15:31:27.821",
    "email": "collins@gmail.com"
  }
}

4.7. Executing a JPQL Query (GET)

Before the execution with the REST API a query must be described in the configuration file. The rest-queries.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.queriesConfig = +com/company/sales/rest-queries.xml

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>

To execute a JPQL query the following GET request must be executed:

http://localhost:8080/app/rest/v2/queries/sales$Order/ordersAfterDate?startDate=2016-11-01&endDate=2017-11-01

The request URL parts:

  • sales$Order - extracted entity name.

  • ordersAfterDate - a query name from the configuration file.

  • startDate and endDate - request parameters with the values.

An OAuth token must be put in the Authorization header with the Bearer type.

The method returns a JSON array of extracted entity instances:

[
  {
    "_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"
    }
  }
]

A full list of possible request parameters is available in the Swagger documentation.

4.8. Executing a JPQL Query (POST)

It is also possible to execute a query with POST HTTP request. POST request can be used when you need to pass a collection as query parameter value. In this case, the type of the query parameter in REST queries configuration file must end with square brackets: java.lang.String[], java.util.UUID[], etc.

<?xml version="1.0"?>
<queries xmlns="http://schemas.haulmont.com/cuba/rest-queries.xsd">
    <query name="ordersByIds" entity="sales$Order" view="order-edit-view">
        <jpql><![CDATA[select o from sales$Order o where o.id in :ids and o.status = :status]]></jpql>
        <params>
            <param name="ids" type="java.util.UUID[]"/>
            <param name="status" type="java.lang.String"/>
        </params>
    </query>
</queries>

Query parameters values must be passed in the request body as JSON map:

{
  "ids": ["c273fca1-33c2-0229-2a0c-78bc6d09110a", "e6c04c18-c8a1-b741-7363-a2d58589d800", "d268a4e1-f316-a7c8-7a96-87ba06afbbbd"],
  "status": "ready"
}

The POST request URL:

http://localhost:8080/app/rest/v2/queries/sales$Order/ordersByIds?returnCount=true

4.9. Service Method Invocation (GET)

Suppose there is an OrderService service in the system. The implementation looks as follows:

package com.company.sales.service;

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 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/rest-services.xml

rest-services.xml content:

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

The request URL parts:

  • sales_OrderService - a service name.

  • calculatePrice - a method name.

  • orderNumber - an argument name with the value.

An OAuth token must be put in 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

4.10. Service Method Invocation (POST)

REST API allows execution not only of methods that have arguments of simple datatypes, but also of methods with the following arguments:

  • entities

  • entities collections

  • serializable 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;
}

OrderValidationResult class looks as follows:

package com.company.sales.service;

import java.io.Serializable;

public class OrderValidationResult implements Serializable {

    private boolean success;

    private String errorMessage;

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }
}

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 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 put in the Authorization header with the Bearer type.

The REST API method returns a serialized POJO:

{
  "success": false,
  "errorMessage": "Validation of order 00050 failed. validationDate parameter is: 2016-10-01"
}

4.11. Files Downloading

When downloading a file, passing a security token in the request header is often inconvenient. It is desirable to have a URL for downloading that may be put to the src attribute of the img tag.

As a solution, an OAuth token can also be passed in the request URL as a parameter with the access_token name.

For example, an image is uploaded to the application. Its FileDescriptor id is 44809679-e81c-e5ae-dd81-f56f223761d6.

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

4.12. Files Uploading

In order to upload a file, you should get an access token which will be used in the subsequent requests.

Suppose we have the following form for the file input:

<form id="fileForm">
    <h2>Select a file:</h2>
    <input type="file" name="file" id="fileUpload"/>
    <br/>
    <button type="submit">Upload</button>
</form>

<h2>Result:</h2>
<img id="uploadedFile" src="" style="display: none"/>

We will use jQuery for the upload and get a JSON with data which is the newly created FileDescriptor instance. We can access the uploaded file by its FileDescriptor id with the access token as a parameter:

$('#fileForm').submit(function (e) {
    e.preventDefault();

    var file = $('#fileUpload')[0].files[0];
    var url = 'http://localhost:8080/app/rest/v2/files?name=' + file.name; // send file name as parameter

    $.ajax({
        type: 'POST',
        url: url,
        headers: {
            'Authorization': 'Bearer ' + oauthToken // add header with access token
        },
        processData: false,
        contentType: false,
        dataType: 'json',
        data: file,
        success: function (data) {
            alert('Upload successful');

            $('#uploadedFile').attr('src',
                'http://localhost:8080/app/rest/v2/files/' + data.id + '?access_token=' + oauthToken); // update image url
            $('#uploadedFile').show();
        }
    });
});

4.13. JavaScript Usage Example

This section contains an example of using REST API v2 from JavaScript running on a HTML page. The page initially shows login form, and after successful login displays a message and a list of entities.

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

customers.html file content:

<html>
    <head>
        <script type="text/javascript" src="jquery-3.1.1.min.js"></script>
        <link rel="stylesheet" href="bootstrap.min.css"/>
    </head>
    <body>
        <div style="width: 300px; margin: auto;">
            <h1>Sales</h1>

            <div id="loggedInStatus" style="display: none" class="alert alert-success">
                Logged in successfully
            </div>
            <div id="loginForm">
                <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>

            <div id="customers" style="display: none">
                <h2>Customers</h2>
                <ul id="customersList"></ul>
            </div>
        </div>
        <script type="text/javascript" src="customers.js"></script>
    </body>
</html>

customers.js file content:

var oauthToken = null;
function login() {
    var userLogin = $('#loginField').val();
    var userPassword = $('#passwordField').val();
    $.post({
        url: 'http://localhost:8080/app/rest/v2/oauth/token',
        headers: {
            'Authorization': 'Basic Y2xpZW50OnNlY3JldA==',
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        dataType: 'json',
        data: {grant_type: 'password', username: userLogin, password: userPassword},
        success: function (data) {
            oauthToken = data.access_token;
            $('#loggedInStatus').show();
            $('#loginForm').hide();
            loadCustomers();
        }
    })
}

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();
            $.each(data, function (i, customer) {
                $('#customersList').append("<li>" + customer.name + " (" + customer.email + ")</li>");
            });
        }
    });
}

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.

rest js 1
rest js 2

4.14. Getting Localized Messages

There are methods in the REST API for getting localized messages for entities, their properties and enums.

For example, to get a list of localized messages for the sec$User entity you have to execute the following GET request:

http://localhost:8080/app/rest/v2/messages/entities/sec$User

An OAuth token must be put in the Authorization header with the Bearer type.

You can explicitly specify the desired locale using the Accept-Language http header.

The response will be like this:

{
  "sec$User": "User",
  "sec$User.active": "Active",
  "sec$User.changePasswordAtNextLogon": "Change Password at Next Logon",
  "sec$User.createTs": "Created At",
  "sec$User.createdBy": "Created By",
  "sec$User.deleteTs": "Deleted At",
  "sec$User.deletedBy": "Deleted By",
  "sec$User.email": "Email",
  "sec$User.firstName": "First Name",
  "sec$User.group": "Group",
  "sec$User.id": "ID",
  "sec$User.ipMask": "Permitted IP Mask",
  "sec$User.language": "Language",
  "sec$User.lastName": "Last Name",
  "sec$User.login": "Login",
  "sec$User.loginLowerCase": "Login",
  "sec$User.middleName": "Middle Name",
  "sec$User.name": "Name",
  "sec$User.password": "Password",
  "sec$User.position": "Position",
  "sec$User.substitutions": "Substitutions",
  "sec$User.timeZone": "Time Zone",
  "sec$User.timeZoneAuto": "Autodetect Time Zone",
  "sec$User.updateTs": "Updated At",
  "sec$User.updatedBy": "Updated By",
  "sec$User.userRoles": "User Roles",
  "sec$User.version": "Version"
}

To get the localization for enum, use the following URL:

http://localhost:8080/app/rest/v2/messages/enums/com.haulmont.cuba.security.entity.RoleType

If you omit the entity name or enum name part in the URL, you’ll get the localization for all entities or enums.

4.15. Data Model Versioning Example

Entity attribute was renamed

Let’s suppose that the oldNumber attribute of the sales$Order entity was renamed to newNumber and date was renamed to deliveryDate. In this case transformation config will be like this:

<?xml version="1.0"?>
<transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">
    <transformation modelVersion="1.0" currentEntityName="sales$Order">
        <renameAttribute oldName="oldNumber" currentName="newNumber"/>
        <renameAttribute oldName="date" currentName="deliveryDate"/>
    </transformation>
    ...
</transformations>

If the client app needs to work with the old version of the sales$Order entity then it must pass the modelVersion value in the URL parameter:

http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.0

The following result will be returned:

{
  "_entityName": "sales$Order",
  "_instanceName": "00001",
  "id": "46322d73-2374-1d65-a5f2-160461da22bf",
  "date": "2016-10-31",
  "description": "Vacation order",
  "oldNumber": "00001"
}

The response JSON contains an oldNumber and date attributes although the entity in the CUBA application has newNumber and deliveryDate attributes.

Entity name was changed

Next, let’s imagine, that in some next release of the application a name of the sales$Order entity was also changed. The new name is sales$NewOrder.

Transformation config for version 1.1 will be like this:

<?xml version="1.0"?>
<transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">
    <transformation modelVersion="1.1" oldEntityName="sales$Order" currentEntityName="sales$NewOrder">
        <renameAttribute oldName="oldNumber" currentName="newNumber"/>
    </transformation>
    ...
</transformations>

In addition to the config from the previous example an oldEntityName attribute is added here. It specifies the entity name that was valid for model version 1.1. The currentEntityName attribute specifies the current entity name.

Although an entity with a name sales$Order doesn’t exist anymore, the following request will work:

http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.1

The REST API controller will understand that it must search among sales$NewOrder entities and after the entity with given id is found names of the entity and of the newNumber attribute will be replaced in the result JSON:

{
  "_entityName": "sales$Order",
  "_instanceName": "00001",
  "id": "46322d73-2374-1d65-a5f2-160461da22bf",
  "date": "2016-10-31",
  "description": "Vacation order",
  "oldNumber": "00001"
}

The client app can also use the old version of data model for entity update and creation.

This POST request that uses old entity name and has old JSON in the request body will work:

http://localhost:8080/app/rest/v2/entities/sales$Order

{
  "_entityName": "sales$Order",
  "_instanceName": "00001",
  "id": "46322d73-2374-1d65-a5f2-160461da22bf",
  "date": "2016-10-31",
  "description": "Vacation order",
  "oldNumber": "00001"
}
Entity attribute must be removed from JSON

If some attribute was added to the entity, but the client that works with the old version of data model doesn’t expect this new attribute, then the new attribute can be removed from the result JSON.

Transformation configuration for this case will look like this:

<?xml version="1.0"?>
<transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">
    <transformation modelVersion="1.5" currentEntityName="sales$Order">
        <toVersion>
            <removeAttribute name="discount"/>
        </toVersion>
    </transformation>
    ...
</transformations>

Transformation in this config file contains a toVersion tag with a nested removeAttribute command. This means that when the transformation from the current state to specific version is performed (i.e. when you request a list of entities) then a discount attribute must be removed from the result JSON.

In this case if you perform the request without the modelVersion attribute, the discount attribute will be returned:

http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93

{
    "_entityName": "sales$Order",
    "_instanceName": "00001",
    "id": "46322d73-2374-1d65-a5f2-160461da22bf",
    "deliveryDate": "2016-10-31",
    "description": "Vacation order",
    "number": "00001",
    "discount": 50
}

If you specify the modelVersion then discount attribute will be removed

http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.1

{
    "_entityName": "sales$Order",
    "_instanceName": "00001",
    "id": "46322d73-2374-1d65-a5f2-160461da22bf",
    "deliveryDate": "2016-10-31",
    "description": "Vacation order",
    "oldNumber": "00001"
}
Using custom transformer

You can also create and register a custom JSON transformer. As an example let’s examine the following situation: there was an entity sales$OldOrder that was renamed to sales$NewOrder. This entity has an orderDate field. In the previous version, this date field contained a time part, but in the latest version of the entity, the time part is removed. REST API client that request the entity with an old model version 1.0 expects the date field to have the time part, so the transformer must modify the value in the JSON.

First, that’s how the transformer configuration must look like:

<?xml version="1.0"?>
<transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">

    <transformation modelVersion="1.0" oldEntityName="sales$OldOrder" currentEntityName="sales$NewOrder">
        <custom>
            <fromVersion transformerBeanRef="sales_OrderJsonTransformerFromVersion"/>
            <toVersion transformerBeanRef="sales_OrderJsonTransformerToVersion"/>
        </custom>
    </transformation>

    ...
</transformations>

There are a custom element and nested toVersion and fromVersion elements. These elements have a reference to the transformer bean. This means that custom transformer must be registered as a Spring bean. There is one important thing here: a custom transformer may use the RestTransformations platform bean (this bean gives an access to other entities transformers if it is required). But the RestTransformations bean is registered in the Spring context of the REST API servlet, not in the main context of the web application. This means that custom transformer beans must be registered in the REST API Spring context as well.

That’s how we can do that.

First, create a rest-dispatcher-spring.xml in the web or portal module (e.g. in package com.company.test).

Next, register this file in the app.properties of the web or portal module:

cuba.restSpringContextConfig = +com/company/test/rest-dispatcher-spring.xml

The rest-dispatcher-spring.xml must contain custom transformer bean definitions:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

    <bean name="sales_OrderJsonTransformerFromVersion" class="com.company.test.transformer.OrderJsonTransformerFromVersion"/>
    <bean name="sales_OrderJsonTransformerToVersion" class="com.company.test.transformer.OrderJsonTransformerToVersion"/>

</beans>

The content of the sales_OrderJsonTransformerToVersion transformer is as follows:

package com.company.test.transformer;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import com.haulmont.restapi.transform.AbstractEntityJsonTransformer;
import com.haulmont.restapi.transform.JsonTransformationDirection;

public class OrderJsonTransformerToVersion extends AbstractEntityJsonTransformer {

    public OrderJsonTransformerToVersion() {
        super("sales$NewOrder", "sales$OldOrder", "1.0", JsonTransformationDirection.TO_VERSION);
    }

    @Override
    protected void doCustomTransformations(ObjectNode rootObjectNode, ObjectMapper objectMapper) {
        JsonNode orderDateNode = rootObjectNode.get("orderDate");
        if (orderDateNode != null) {
            String orderDateNodeValue = orderDateNode.asText();
            if (!Strings.isNullOrEmpty(orderDateNodeValue))
                rootObjectNode.put("orderDate", orderDateNodeValue + " 00:00:00.000");
        }
    }
}

This transformer finds the orderDate node in the JSON object and modifies its value by adding the time part to the value.

When the sales$OldOrder entity with a data model version 1.0 is requested, the result JSON will contain entities with orderDate fields that contain time part, although it is not stored in the database anymore.

A couple more words about custom transformers. They must implement the EntityJsonTransformer interface. You can also extend the AbstractEntityJsonTransformer class and override its doCustomTransformations method. The AbstractEntityJsonTransformer contains all functionality of the standard transformer.

4.16. Using Entities Search Filter

REST API allows you to specify ad-hoc search criteria when getting a list of entities.

Let’s suppose that we have two entities:

  • Author that has two fields: lastName and firstName

  • Book with three fields: title (String), author (Author) and publicationYear (Integer)

To perform a search with conditions we must use the URL like this:

http://localhost:8080/app/rest/v2/entities/test$Book/search

The search conditions must be passed in the filter parameter. It is a JSON object that contains a set of conditions. If the search is performed with the GET request, then the filter parameter must be passed in the URL.

Example 1

We need to find all books that were released in 2007 and have an author with the first name starting with "Alex". The filter JSON should look like this:

{
    "conditions": [
        {
            "property": "author.firstName",
            "operator": "startsWith",
            "value": "Alex"
        },
        {
            "property": "publicationDate",
            "operator": "=",
            "value": 2007
        }
    ]
}

By default, search criteria are applied with the AND operation.

This example also demonstrates that nested properties are supported (author.firstName).

Example 2

The next example demonstrates two things: how to execute a search with the POST request and how to use OR groups. In case of POST request all parameters must be passed in the JSON object that is passed in the request body. The search filter must be placed in the object field called filter. All other parameters (view name, limit, etc.) must be placed in fields with corresponding names:

{
  "filter": {
    "conditions": [
      {
        "group": "OR",
        "conditions": [
          {
            "property": "author.lastName",
            "operator": "contains",
            "value": "Stev"
          },
          {
            "property": "author.lastName",
            "operator": "=",
            "value": "Dumas"
          }
        ]
      },
      {
        "property": "publicationDate",
        "operator": "=",
        "in": [2007, 2008]
      }
    ]
  },
  "view": "book-view"
}

In this example, conditions collection contains not only condition objects, but also an OR group. So the result search criterion will be:

((author.lastName contains Stev) OR (author.lastName = Duma) AND (publicationDate in [2007, 2008]))

Notice that the view parameter is also passed in the request body.

5. Security Implications When Using REST API

Universal REST API provides powerful means to deal with arbitrary data from domain model without creation of custom REST endpoints. However, if you enable the REST API in your application, you must configure security (Roles/Access Groups) and always keep it in actual state on a production system in order to prevent access to some sensitive data.

Here are some rules you should follow when the REST API is turned on:

  • Always disable access to the REST API for roles/users which are not supposed to use it. See CUBA > REST API > Use REST API specific permission in the role editor screen.

  • Use deny-by-default strategy: always assign DENYING role to users which have access to the REST API. Project model changes over time - it’s easy to miss adding granular restrictions.

  • Always configure and assign access group constraints for non-common entities to the REST API users.

  • Remember that EntityManager does not impose access group constraints, so consider using DataManager in middleware services exposed to the REST API.

  • Remember that entity attribute restrictions are not imposed by a DENYING role. Setup permissions for each entity attribute individually.

  • Specify a unique client secret in production environment.

If you have troubles configuring fine-grained security for your project, consider implementing custom endpoints instead of the universal REST API.

Appendix A: Application Properties

cuba.rest.allowedOrigins

Defines a comma-separated list of origins that can access the .

Default value: *

Used in the Web Client and Web Portal blocks.

cuba.rest.anonymousEnabled

Enables an anonymous access to the endpoints.

Default value: false

Used in the Web Client and Web Portal blocks.

cuba.rest.client.authorizedGrantTypes

Defines a list of supported grant types for the default REST API client. To disable refresh token support remove the refresh_token item from the value.

Default value: password,external,refresh_token

Used in the Web Client and Web Portal blocks.

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 . Client credentials are used for basic authentication when accessing the REST API token endpoint.

Default value: client

Used in the Web Client and Web Portal blocks.

cuba.rest.client.secret

Defines a password for the REST API client. Client, in this case, is not a platform user, but an application (some web portal or mobile app) that uses . Client credentials are used for basic authentication when accessing the REST API token endpoint. The application property value in addition to the actual password value (e.g. secret) must contain an identifier of the PasswordEncoder (e.g. {noop}).

Default value: {noop}secret

Used in the Web Client and Web Portal blocks.

cuba.rest.client.tokenExpirationTimeSec

Defines a access token expiration timeout for the default client in seconds.

Default value: 43200 (12 hours)

Used in the Web Client and Web Portal blocks.

cuba.rest.client.refreshTokenExpirationTimeSec

Defines a refresh token expiration timeout for the default client in seconds.

Default value: 31536000 (365 days)

Used in the Web Client and Web Portal blocks.

cuba.rest.deleteExpiredTokensCron

Specifies cron expression for scheduled removing of expired tokens from the database.

Default value: 0 0 3 * * ?

Used in the Middleware block.

cuba.rest.jsonTransformationConfig

Additive property defining a file that contains JSON transformation configurations used by the when the client needs data in format of some particular data model version.

The file is loaded using the Resources interface, so it can be located in classpath or in the configuration directory.

The XSD of the file is available at {xsd_url}/rest-json-transformations.xsd.

Default value: none

Example:

cuba.rest.jsonTransformationConfig = +com/company/sample/json-transformations.xml

Used in the Web Client and Web Portal blocks.

cuba.rest.maxUploadSize

Maximum file size (in bytes) that can be uploaded with the .

Default value: 20971520 (20 Mb)

Used in the Web Client and Web Portal blocks.

cuba.rest.optimisticLockingEnabled

Enables optimistic locking of Versioned entities if the version attribute is provided in JSON.

Default value: false

Used in the Web Client and Web Portal blocks.

cuba.rest.requiresSecurityToken

If true, a special system attribute is included in JSON for loaded entities, and the same attribute is expected to be passed back to REST when saving the entities. See details in Security Constraints for Collection Attributes.

Default value: false

Used in the Web Client and Web Portal blocks.

cuba.rest.reuseRefreshToken

Specifies whether a refresh token may be reused. If set to false then when an access token is requested using the refresh token, a new refresh token will be issued, and the old refresh token will be revoked.

Default value: true

Used in the Web Client and Web Portal blocks.

cuba.rest.servicesConfig

Additive property defining a file that contains a list of services available for application calls.

The file is loaded using the Resources interface, so it can be located in classpath or in the configuration directory.

The XSD of the file is available at {xsd_url}/rest-services-v2.xsd.

Default value: none

Example:

cuba.rest.servicesConfig = +com/company/sample/app-rest-services.xml

Used in the Web Client and Web Portal blocks.

cuba.rest.storeTokensInDb

Enables storing of REST API security tokens in the database. By default, tokens are stored in memory only.

Stored in the database.

Interface: ServerConfig

Default value: false

Used in the Middleware block.

cuba.rest.tokenMaskingEnabled

Specifies whether REST API token values should be masked in application logs.

Default value: true

Used in the Web Client and Web Portal blocks.

cuba.rest.queriesConfig

Additive property defining a file that contains a list of JPQL queries available for application calls.

The file is loaded using the Resources interface, so it can be located in classpath or in the configuration directory.

The XSD of the file is available at {xsd_url}/rest-queries.xsd.

Default value: none

Example:

cuba.rest.queriesConfig = +com/company/sample/app-rest-queries.xml

Used in the Web Client and Web Portal blocks.