-
Notifications
You must be signed in to change notification settings - Fork 7
Data Model Versioning Example
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.
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"
}
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"
}
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.
- Home
- Predefined JPQL Queries Configuration
- Services Configuration
- Data Model Versioning
- CORS Settings
- Anonymous Access
- Other REST API Settings
- Creating Custom OAuth2 Protected Controllers
- Security Constraints for Collection Attributes
- Persistent Token Store
- Project-specific Swagger Documentation
- Application Properties
-
Using REST API
- Getting an OAuth Token
- REST API Authentication with LDAP
- Custom Authentication
- Getting an Entity Instances List
- New Entity Instance Creation
- Existing Entity Instance Update
- Executing a JPQL Query (GET)
- Executing a JPQL Query (POST)
- Service Method Invocation (GET)
- Service Method Invocation (POST)
- Files Downloading
- Files Uploading
- JavaScript Usage Example
- Getting Localized Messages
- Data Model Versioning Example
- Using Entities Search Filter