Getting started Community Training Tutorials Documentation APIs, AI & Tools
This example covers the necessary steps for successfully migrate an APIkit-based application from Mule 3 to Mule 4. The following REST API performs CRUD (create, read, update and delete) operations over a MySQL database.
Mule 4 supports property placeholders either as .yaml or .properties configuration files.
Create a folder with the name config under /src/main/resources project directory.
Create a configuration file with the name configuration.yaml inside the newly created config folder.
Migrate property placeholders from .properties to .yaml format.
http.host=0.0.0.0 http.port=8081 mysql.password=pa$$w0rd mysql.port=3306 mysql.user=admin mysql.database=products mysql.host=corp.services.com autodiscovery.api.version=1.0.0:1762946 autodiscovery.api.name=groupId:com.mulesoft.retailer.manufacturingit.apis:assetId:product-api-database anypoint.platform.client_id=1f702j71hu9z2x88v9vd19v7h248s589 anypoint.platform.client_secret=87v8V47668701Dd574B0531255d6287d
http:
host: "0.0.0.0"
port: "8081"
mysql:
password: "pa$$w0rd"
port: "3306"
user: "admin"
database: "products"
host: "corp.services.com"
autodiscovery:
api:
name: "groupId:com.mulesoft.retailer.manufacturingit.apis:assetId:product-api-database"
version: "1.0.0:1762946"
anypoint:
platform:
client_id: "1f702j71hu9z2x88v9vd19v7h248s589"
client_secret: "87v8V47668701Dd574B0531255d6287d"
Replace the standard Spring element <context:property-placeholder> with the new Global Element configuration-properties.
<context:property-placeholder location="configuration.properties" />
The file attribute points to the new configuration file in YAML format located under /src/main/resources/config folder.
<configuration-properties file="config/configuration.yaml" doc:name="Configuration properties" />
<http:listener-config name="HTTP_Listener_Configuration" host="${http.host}" port="${http.port}" doc:name="HTTP Listener Configuration"/>
The minimal configuration requires specifying host and port in the inner http:listener-connection element. We use the placeholders defined in the previous step.
<http:listener-config name="httpListenerConfig">
<http:listener-connection host="${http.host}" port="${http.port}" />
</http:listener-config>
The database connector facilitates setting up Derby, MySQL and Oracle databases for use in a Mule app.
In the Mule Palette, click Add Module and select Database Connector among the available modules.
Add a Database Config Global Configuration Element.
In Database Config > Connection, select MySQL Connection.
In MySQL JDBC Driver, click Add Dependency and configure the Maven information for the driver.
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency>
Complete MySQL Global Configuration with host, port, user, password and database using previously defined placeholders.
<db:mysql-config name="MySQL_Configuration" host="${mysql.host}" port="${mysql.port}" user="${mysql.user}" password="${mysql.password}" database="${mysql.database}" doc:name="MySQL Configuration" />
<db:config name="MySQL_Configuration" doc:name="Database Config">
<db:my-sql-connection host="${mysql.host}" port="${mysql.port}" user="${mysql.user}" password="${mysql.password}" database="${mysql.database}" />
</db:config>
The api-platform-gw global element is required for registering an API in Anypoint Platform.
<api-platform-gw:api apiName="${autodiscovery.api.name}" version="${autodiscovery.api.version}" flowRef="api-main" create="true" doc:name="API Autodiscovery"/>
In Mule Runtime 4.x, the apiName, version, and create attributes were removed. Just the apiId and flowRef attributes are required. apiId is generated by API Manager and visible on the API instance dashboard.
For API Autodiscovery Configuration in Mule Runtime 4.x:
Add the following Namespace, Schema global.xml Configuration file.
xmlns:api-gateway="http://www.mulesoft.org/schema/mule/api-gateway" http://www.mulesoft.org/schema/mule/api-gateway http://www.mulesoft.org/schema/mule/api-gateway/current/mule-api-gateway.xsd
Add the required Autodiscovery Dependency Information to project pom.xml file.
<dependency> <groupId>com.mulesoft.anypoint</groupId> <artifactId>mule-module-autodiscovery</artifactId> <version>4.0.0</version> </dependency>
<api-gateway:autodiscovery apiId="${autodiscovery.api.id}" flowRef="api-product-main" doc:name="API Autodiscovery"/>
<validation:config name="Validation_Configuration" doc:name="Validation Configuration"/>
Opposite to Mule Runtime 3.x, adding the Validation Module to the Mule Palette is required to proceed with the configuration.
In the Mule Palette, click Add Module and select Validation Module among the available modules.
Add a Validation Config Global Configuration Element.
<validation:config name="Validation_Config" doc:name="Validation Config" />
get-products-flow returns products from the database filtering by Product Category and/or Product Name also supporting paginated queries with offset and maxResults parameters.
<flow name="get-products-flow">
<message-properties-transformer doc:name="Get Query Params" scope="invocation">
<add-message-property key="queryOffset" value="#[Integer.valueOf(message.inboundProperties.'http.query.params'.offset)]" />
<add-message-property key="queryLimit" value="#[Integer.valueOf(message.inboundProperties.'http.query.params'.maxResults)]" />
<add-message-property key="queryName" value="#[ (message.inboundProperties.'http.query.params'.name != null) ? ('%'+message.inboundProperties.'http.query.params'.name+'%') : '%%']" />
<add-message-property key="queryCategory" value="#[ (message.inboundProperties.'http.query.params'.category != null) ? ('%'+message.inboundProperties.'http.query.params'.category+'%') : '%%']" />
</message-properties-transformer>
<db:select config-ref="MySQL_Configuration" doc:name="Query Products">
<db:parameterized-query><![CDATA[SELECT p.id, p.name, p.description, p.product_number, p.manufactured, p.colors, p.categories, p.stock, p.safety_stock_level, p.standard_cost, p.list_price, p.size, p.size_unit_measure_code, p.weight, p.weight_unit_measure_code, p.days_to_manufacture, p.images, p.modified_date, p.created_date
FROM product p
WHERE LOWER(p.name) like #[flowVars.queryName.toLowerCase()] AND LOWER(p.categories) like #[flowVars.queryCategory.toLowerCase()]
LIMIT #[flowVars.queryLimit]
OFFSET #[flowVars.queryOffset]]]>
</db:parameterized-query>
</db:select>
<dw:transform-message doc:name="Products to JSON">
<dw:set-payload resource="classpath:mappings/get-products-response.dwl"/>
</dw:transform-message>
</flow>
There are no changes regarding get-products-flow definition.
<flow name="get-products-flow">
Create a package with the name variables under src/main/resources folder.
Create the file set-queryCategory-variable.dwl under src/main/resources/variables folder and write a DW script for setting queryCategory flow variable.
%dw 2.0 output application/java var queryCategory = attributes.queryParams.category --- if (queryCategory != null) queryCategory else '%%'
Create the file set-queryLimit-variable.dwl under src/main/resources/variables folder and write a DW script for setting queryLimit flow variable.
%dw 2.0 output application/java --- attributes.queryParams.maxResults as Number
Create the file set-queryName-variable.dwl under src/main/resources/variables folder and write a DW script for setting queryName flow variable.
%dw 2.0 output application/java var queryName = attributes.queryParams.name --- if (queryName != null) queryName else '%%'
Create the file set-queryOffset-variable.dwl under src/main/resources/variables folder and write a DW script for setting queryOffset flow variable.
%dw 2.0 output application/java --- attributes.queryParams.offset as Number
Add a Transform component to replace the logic inside message-properties-transformer and set the variables queryOffset, queryLimit, queryName and queryCategory referencing to its DW script.
<flow name="get-products-flow">
<ee:transform doc:name="Get Query Params" doc:id="ab756164-e1df-4fc5-8fbe-8f4f8cafc2f6">
<ee:message />
<ee:variables>
<ee:set-variable variableName="queryOffset" resource="variables/set-queryOffset-variable.dwl" />
<ee:set-variable variableName="queryLimit" resource="variables/set-queryLimit-variable.dwl" />
<ee:set-variable variableName="queryName" resource="variables/set-queryName-variable.dwl" />
<ee:set-variable variableName="queryCategory" resource="variables/set-queryCategory-variable.dwl" />
</ee:variables>
</ee:transform>
</flow>
Add a db:select element, referencing the MySQL Global Configuration. Use the colon (:) syntax in parametrized queries. Parameters must be supplied as key-value pairs into the db:input-parameters element.
<db:select config-ref="MySQL_Configuration" doc:name="Query Products">
<db:sql >SELECT p.id, p.name, p.description, p.product_number, p.manufactured, p.colors, p.categories, p.stock, p.safety_stock_level, p.standard_cost, p.list_price, p.size, p.size_unit_measure_code, p.weight, p.weight_unit_measure_code, p.days_to_manufacture, p.images, p.modified_date, p.created_date
FROM product p
WHERE LOWER(p.name) like :name AND LOWER(p.categories) like :category
LIMIT :limit
OFFSET :offset</db:sql>
<db:input-parameters ><![CDATA[#[{'name' : lower(vars.queryName), 'category': lower(vars.queryCategory), 'limit': vars.queryLimit, 'offset': vars.queryOffset}]]]></db:input-parameters>
</db:select>
Create a package with the name mappings under src/main/resources folder.
Create the file get-products-response.dwl under src/main/resources/mappings folder.
Migrate get-products-response.dwl DW 1.0 script to DW 2.0.
%dw 1.0
%output application/json
---
payload map {
id: $.id,
categories: ($.categories default "") splitBy ",",
colors: ($.colors default "") splitBy ",",
images: ($.images default "") splitBy ",",
createdDate: $.created_date as :string {format: "yyyy-MM-dd"},
modifiedDate: $.modified_date as :string {format: "yyyy-MM-dd"},
safetyStockLevel: $.safety_stock_level as :number,
stock: $.stock as :number,
daysToManufacture: $.days_to_manufacture,
name: $.name,
description: $.description,
listPrice: $.list_price,
manufactured: $.manufactured,
productNumber: $.product_number,
size: $.size,
sizeUnitMeasureCode: $.size_unit_measure_code,
standardCost: $.standard_cost,
weightUnitMeasureCode: $.weight_unit_measure_code,
weight: $.weight
}
%dw 2.0
output application/json
---
payload map {
id: $.id,
categories: ($.categories default "") splitBy ",",
colors: ($.colors default "") splitBy ",",
images: ($.images default "") splitBy ",",
createdDate: $.created_date as String {format: "yyyy-MM-dd"},
modifiedDate: $.modified_date as String {format: "yyyy-MM-dd"},
safetyStockLevel: $.safety_stock_level as Number,
stock: $.stock as Number,
daysToManufacture: $.days_to_manufacture,
name: $.name,
description: $.description,
listPrice: $.list_price,
manufactured: $.manufactured,
productNumber: $.product_number,
size: $.size,
sizeUnitMeasureCode: $.size_unit_measure_code,
standardCost: $.standard_cost,
weightUnitMeasureCode: $.weight_unit_measure_code,
weight: $.weight
}
Finally, add a Transform component that sets the payload using the DW 2.0 transformation.
<flow name="get-products-flow">
<!-- more logic here -->
<ee:transform doc:name="Products to JSON">
<ee:message>
<ee:set-payload resource="mappings/get-products-response.dwl" />
</ee:message>
</ee:transform>
</flow>
<flow name="get-products-flow">
<ee:transform doc:name="Get Query Params" doc:id="ab756164-e1df-4fc5-8fbe-8f4f8cafc2f6">
<ee:message />
<ee:variables>
<ee:set-variable variableName="queryOffset" resource="variables/set-queryOffset-variable.dwl" />
<ee:set-variable variableName="queryLimit" resource="variables/set-queryLimit-variable.dwl" />
<ee:set-variable variableName="queryName" resource="variables/set-queryName-variable.dwl" />
<ee:set-variable variableName="queryCategory" resource="variables/set-queryCategory-variable.dwl" />
</ee:variables>
</ee:transform>
<db:select config-ref="MySQL_Configuration" doc:name="Query Products">
<db:sql>SELECT p.id, p.name, p.description, p.product_number,
p.manufactured, p.colors, p.categories, p.stock,
p.safety_stock_level, p.standard_cost, p.list_price, p.size,
p.size_unit_measure_code, p.weight, p.weight_unit_measure_code,
p.days_to_manufacture, p.images, p.modified_date, p.created_date
FROM product p
WHERE LOWER(p.name) like :name AND LOWER(p.categories) like :category
LIMIT :limit
OFFSET :offset</db:sql>
<db:input-parameters><![CDATA[#[{'name' : lower(vars.queryName), 'category': lower(vars.queryCategory), 'limit': vars.queryLimit, 'offset': vars.queryOffset}]]]></db:input-parameters>
</db:select>
<ee:transform doc:name="Products to JSON">
<ee:message>
<ee:set-payload resource="mappings/get-products-response.dwl" />
</ee:message>
</ee:transform>
</flow>
get-product-by-id-flow returns a product from the database filtering by id. If there isn’t a product with the required id, an HTTP 404 Not Found error is returned.
<flow name="get-product-by-id-flow">
<db:select config-ref="MySQL_Configuration" doc:name="Get by Id">
<db:parameterized-query><![CDATA[SELECT p.id, p.name, p.description, p.product_number, p.manufactured, p.colors, p.categories, p.stock, p.safety_stock_level, p.standard_cost, p.list_price, p.size, p.size_unit_measure_code, p.weight, p.weight_unit_measure_code, p.days_to_manufacture, p.images, p.modified_date, p.created_date FROM product p where p.id = #[id]]]></db:parameterized-query>
</db:select>
<validation:is-true config-ref="Validation_Configuration" doc:name="Is Not Empty" exceptionClass="org.mule.module.apikit.exception.NotFoundException" expression="#[payload.size() > 0]"/>
<dw:transform-message doc:name="Product to JSON">
<dw:set-payload resource="classpath:mappings/get-product-by-id-response.dwl"/>
</dw:transform-message>
</flow>
There are no changes regarding get-product-by-id-flow definition.
<flow name="get-product-by-id-flow" />
Create set-productId-variable.dwl under src/main/resources/variables folder. Add the following logic for getting the id from uriParams.
%dw 2.0 output application/java --- attributes.uriParams.id
Add a Transform component that references the DW script that sets a variable with the productId value received as a URI parameter.
<flow name="get-product-by-id-flow">
<ee:transform doc:name="Get Uri Params">
<ee:message />
<ee:variables>
<ee:set-variable variableName="id" resource="variables/set-productId-variable.dwl" />
</ee:variables>
</ee:transform>
</flow>
Add a db:select element, referencing the MySQL Global Configuration. Use the colon (:) syntax in parametrized queries. Parameters must be supplied as key-value pairs into the db:input-parameters element.
<db:select config-ref="MySQL_Configuration" doc:name="Get by Id">
<db:sql>SELECT p.id, p.name, p.description, p.product_number, p.manufactured, p.colors, p.categories, p.stock, p.safety_stock_level, p.standard_cost, p.list_price, p.size, p.size_unit_measure_code, p.weight, p.weight_unit_measure_code, p.days_to_manufacture, p.images, p.modified_date, p.created_date
FROM product p
where p.id = :id</db:sql>
<db:input-parameters><![CDATA[#[{'id' : vars.id}]]]></db:input-parameters>
</db:select>
Add a validation:is-true element after the db:select that checks if the query has returned results. If not, throw an APP:NOT_FOUND error. Notice that as MEL has been replaced by DataWeave as the default expression language, [payload.size() > 0] is rewritten as [sizeOf(payload) > 0].
<validation:is-true doc:name="Is Not Empty" config-ref="Validation_Config" expression="#[sizeOf(payload) > 0]">
<error-mapping sourceType="VALIDATION:INVALID_BOOLEAN" targetType="APP:NOT_FOUND" />
</validation:is-true>
Create get-product-by-id-response.dwl file under src/main/resources/mappings folder and migrate DataWeave script for building JSON response from 1.0 to 2.0.
%dw 1.0
%output application/json
%var product = payload[0]
---
{
id: product.id,
name: product.name,
description: product.description,
manufactured: product.manufactured,
productNumber: product.product_number,
colors: (product.colors default "") splitBy "," ,
categories:(product.categories default "") splitBy "," ,
safetyStockLevel: product.safety_socket_level,
standardCost: (product.standard_cost default "0.0") as :string {format: "##.##"} as :number,
listPrice: (product.list_price default "0.0") as :string {format: "##.##"} as :number,
stock: product.stock,
safetyStockLevel: product.safety_stock_level,
daysToManufacture: product.days_to_manufacture,
size: product.size,
sizeUnitMeasureCode: product.size_unit_measure_code,
weight: product.weight,
weightUnitMeasureCode: product.weight_unit_measure_code,
daysToManufacture: product.days_to_manufacture,
images: (product.images splitBy "," default null),
modifiedDate: (product.modified_date default "") as :date {format: "yyyy-MM-dd"},
createdDate: (product.created_date default "") as :date {format: "yyyy-MM-dd"}
}
%dw 2.0
output application/json
var product = payload[0]
---
{
id: product.id,
name: product.name,
description: product.description,
manufactured: product.manufactured,
productNumber: product.product_number,
colors: (product.colors default "") splitBy "," ,
categories:(product.categories default "") splitBy "," ,
safetyStockLevel: product.safety_socket_level,
standardCost: (product.standard_cost default "0.0") as String {format: "##.##"} as Number,
listPrice: (product.list_price default "0.0") as String {format: "##.##"} as Number,
stock: product.stock,
safetyStockLevel: product.safety_stock_level,
daysToManufacture: product.days_to_manufacture,
size: product.size,
sizeUnitMeasureCode: product.size_unit_measure_code,
weight: product.weight,
weightUnitMeasureCode: product.weight_unit_measure_code,
daysToManufacture: product.days_to_manufacture,
images: (product.images splitBy "," default null),
modifiedDate: (product.modified_date default "") as Date {format: "yyyy-MM-dd"},
createdDate: (product.created_date default "") as Date {format: "yyyy-MM-dd"}
}
Finally, add a Transform component that sets the payload using the DW 2.0 transformation.
<ee:transform doc:name="Product to JSON">
<ee:message>
<ee:set-payload resource="mappings/get-product-by-id-response.dwl" />
</ee:message>
</ee:transform>
<flow name="get-product-by-id-flow">
<ee:transform doc:name="Get Uri Params">
<ee:message />
<ee:variables>
<ee:set-variable variableName="id" resource="variables/set-productId-variable.dwl" />
</ee:variables>
</ee:transform>
<db:select config-ref="MySQL_Configuration" doc:name="Get by Id">
<db:sql>SELECT p.id, p.name, p.description, p.product_number, p.manufactured, p.colors, p.categories, p.stock, p.safety_stock_level, p.standard_cost, p.list_price, p.size, p.size_unit_measure_code, p.weight, p.weight_unit_measure_code, p.days_to_manufacture, p.images, p.modified_date, p.created_date
FROM product p
where p.id = :id</db:sql>
<db:input-parameters><![CDATA[#[{'id' : vars.id}]]]></db:input-parameters>
</db:select>
<validation:is-true doc:name="Is Not Empty" config-ref="Validation_Config" expression="#[sizeOf(payload) > 0]">
<error-mapping sourceType="VALIDATION:INVALID_BOOLEAN" targetType="APP:NOT_FOUND" />
</validation:is-true>
<ee:transform doc:name="Product to JSON">
<ee:message>
<ee:set-payload resource="mappings/get-product-by-id-response.dwl" />
</ee:message>
</ee:transform>
</flow>
post-product-flow inserts a product in the database.
<flow name="post-product-flow">
<set-variable variableName="originalPayload" value="#[payload:java.lang.String]" doc:name="Set Original Payload" />
<dw:transform-message doc:name="Json to Map">
<dw:set-payload resource="classpath:mappings/json-product-to-java.dwl"/>
</dw:transform-message>
<transactional action="ALWAYS_BEGIN" doc:name="Transactional">
<db:insert config-ref="MySQL_Configuration" doc:name="Insert Product" autoGeneratedKeys="true" autoGeneratedKeysColumnNames="id" target="#[payload]">
<db:parameterized-query><![CDATA[insert into product(name, description, product_number, manufactured, colors, categories, stock, safety_stock_level, standard_cost, list_price, size, size_unit_measure_code, weight, weight_unit_measure_code, days_to_manufacture, images, modified_date, created_date) values(#[payload.name],#[payload.description], #[payload.productNumber], #[payload.manufactured], #[payload.colors], #[payload.categories], #[payload.stock], #[payload.safetyStockLevel], #[payload.standardCost], #[payload.listPrice], #[payload.size], #[payload.sizeUnitMeasureCode], #[payload.weight], #[payload.weightUnitMeasureCode], #[payload.daysToManufacture], #[payload.images], CURDATE(), CURDATE() );]]></db:parameterized-query>
</db:insert>
</transactional>
<dw:transform-message doc:name="Database to Json">
<dw:input-variable doc:sample="json.json" mimeType="application/json" variableName="originalPayload" />
<dw:set-payload resource="classpath:mappings/post-product-response.dwl"/>
</dw:transform-message>
</flow>
There are no changes regarding post-product-flow definition.
<flow name="post-product-flow" />
Create a json-to-java.dwl file into src/main/resources/mappings folder to transform the JSON request into a JAVA map.
%dw 2.0 output application/java --- payload
Create a json-product-to-java.dwl file into src/main/resources/mappings and migrate the original script from DW 1.0 to 2.0.
%dw 1.0
%output application/java
---
{
categories: payload.categories joinBy ",",
colors: payload.colors joinBy ",",
daysToManufacture: payload.daysToManufacture,
description: payload.description,
images: payload.images joinBy ",",
listPrice: payload.listPrice,
(manufactured: 1) when payload.manufactured == true,
(manufactured: 0) when payload.manufactured == false,
name: payload.name,
productNumber: payload.productNumber,
safetyStockLevel: payload.safetyStockLevel,
size: payload.size,
sizeUnitMeasureCode: payload.sizeUnitMeasureCode,
standardCost: payload.standardCost,
stock: payload.stock,
weight: payload.weight,
weightUnitMeasureCode: payload.weightUnitMeasureCode
}
%dw 2.0
output application/java
fun getManufacturedCode(value) =
if (value == true) 1
else 0
---
{
categories: payload.categories joinBy ",",
colors: payload.colors joinBy ",",
daysToManufacture: payload.daysToManufacture,
description: payload.description,
images: payload.images joinBy ",",
listPrice: payload.listPrice,
manufactured: getManufacturedCode(payload.manufactured),
name: payload.name,
productNumber: payload.productNumber,
safetyStockLevel: payload.safetyStockLevel,
size: payload.size,
sizeUnitMeasureCode: payload.sizeUnitMeasureCode,
standardCost: payload.standardCost,
stock: payload.stock,
weight: payload.weight,
weightUnitMeasureCode: payload.weightUnitMeasureCode
}
Add a Transform component that sets originalPayload and newPayload using the previously created DataWeave transformations.
<flow name="post-product-flow">
<ee:transform doc:name="Json to Map">
<ee:message />
<ee:variables>
<ee:set-variable variableName="originalPayload" resource="mappings/json-to-java.dwl" />
<ee:set-variable variableName="newPayload" resource="mappings/json-product-to-java.dwl" />
</ee:variables>
</ee:transform>
</flow>
For configuring the details of the transaction, replace the Mule 3.x transactional scope with the new try scope and set the transactionalAction attribute to ALWAYS_BEGIN.
<try doc:name="Try" transactionalAction="ALWAYS_BEGIN">
</try>
Add a db:insert element into the Try scope, referencing the MySQL Global Configuration. Parameters must be supplied as key-value pairs into the db:input-parameters element. Notice also the inclusion of db:auto-generated-keys-column-name tag for setting the payload with the ID that was auto-generated by the Database engine.
<try transactionalAction="ALWAYS_BEGIN" doc:name="Try">
<db:insert config-ref="MySQL_Configuration" doc:name="Insert Product" autoGenerateKeys="true">
<db:sql>insert into product(name, description, product_number,
manufactured, colors, categories, stock, safety_stock_level,
standard_cost, list_price, size, size_unit_measure_code, weight,
weight_unit_measure_code, days_to_manufacture, images,
modified_date, created_date)
values(:name, :description,
:product_number, :manufactured, :colors,
:categories, :stock,
:safety_stock_level, :standard_cost,
:list_price, :size,
:size_unit_measure_code, :weight,
:weight_unit_measure_code,
:days_to_manufacture, :images,
CURDATE(), CURDATE());
</db:sql>
<db:input-parameters><![CDATA[#[{'name': vars.newPayload.name, 'description': vars.newPayload.description, 'product_number': vars.newPayload.productNumber, 'manufactured': vars.newPayload.manufactured, 'colors': vars.newPayload.colors, 'categories': vars.newPayload.categories, 'stock': vars.newPayload.stock, 'safety_stock_level': vars.newPayload.safetyStockLevel, 'standard_cost': vars.newPayload.standardCost, 'list_price': vars.newPayload.listPrice, 'size': vars.newPayload.size, 'size_unit_measure_code': vars.newPayload.sizeUnitMeasureCode, 'weight': vars.newPayload.weight, 'weight_unit_measure_code': vars.newPayload.weightUnitMeasureCode, 'days_to_manufacture': vars.newPayload.daysToManufacture, 'images': vars.newPayload.images}]]]></db:input-parameters>
<db:auto-generated-keys-column-names>
<db:auto-generated-keys-column-name value="id" />
</db:auto-generated-keys-column-names>
</db:insert>
</try>
Create a post-product-response.dwl file under src/main/resources/mappings and migrate the response transformation from DataWeave 1.0 to 2.0. Notice the difference getting the generated id from the payload with the expression payload.generatedKeys.GENERATED_KEY.
%dw 1.0 %output application/json --- flowVars.originalPayload ++ id: payload[0].GENERATED_KEY
%dw 2.0 output application/json --- vars.originalPayload ++ id: payload.generatedKeys.GENERATED_KEY
Add a Transform component and set the payload with the DataWeave script.
<ee:transform doc:name="Database to Json">
<ee:message>
<ee:set-payload resource="mappings/post-product-response.dwl" />
</ee:message>
</ee:transform>
<flow name="post-product-flow">
<ee:transform doc:name="Json to Map">
<ee:message />
<ee:variables>
<ee:set-variable variableName="originalPayload" resource="mappings/json-to-java.dwl" />
<ee:set-variable variableName="newPayload" resource="mappings/json-product-to-java.dwl" />
</ee:variables>
</ee:transform>
<try transactionalAction="ALWAYS_BEGIN" doc:name="Try">
<db:insert config-ref="MySQL_Configuration" doc:name="Insert Product" autoGenerateKeys="true">
<db:sql>insert into product(name, description, product_number,
manufactured, colors, categories, stock, safety_stock_level,
standard_cost, list_price, size, size_unit_measure_code, weight,
weight_unit_measure_code, days_to_manufacture, images,
modified_date, created_date)
values(:name, :description,
:product_number, :manufactured, :colors,
:categories, :stock,
:safety_stock_level, :standard_cost,
:list_price, :size,
:size_unit_measure_code, :weight,
:weight_unit_measure_code,
:days_to_manufacture, :images,
CURDATE(), CURDATE());
</db:sql>
<db:input-parameters><![CDATA[#[{'name': vars.newPayload.name, 'description': vars.newPayload.description, 'product_number': vars.newPayload.productNumber, 'manufactured': vars.newPayload.manufactured, 'colors': vars.newPayload.colors, 'categories': vars.newPayload.categories, 'stock': vars.newPayload.stock, 'safety_stock_level': vars.newPayload.safetyStockLevel, 'standard_cost': vars.newPayload.standardCost, 'list_price': vars.newPayload.listPrice, 'size': vars.newPayload.size, 'size_unit_measure_code': vars.newPayload.sizeUnitMeasureCode, 'weight': vars.newPayload.weight, 'weight_unit_measure_code': vars.newPayload.weightUnitMeasureCode, 'days_to_manufacture': vars.newPayload.daysToManufacture, 'images': vars.newPayload.images}]]]></db:input-parameters>
<db:auto-generated-keys-column-names>
<db:auto-generated-keys-column-name value="id" />
</db:auto-generated-keys-column-names>
</db:insert>
</try>
<ee:transform doc:name="Database to Json">
<ee:message>
<ee:set-payload resource="mappings/post-product-response.dwl" />
</ee:message>
</ee:transform>
</flow>
put-product-flow updates a product in the database based on the specified id.
<flow name="put-product-flow">
<dw:transform-message doc:name="JSon to Product">
<dw:set-payload resource="classpath:mappings/put-json-product-to-java.dwl"/>
</dw:transform-message>
<transactional action="ALWAYS_BEGIN" doc:name="Transactional">
<db:update config-ref="MySQL_Configuration" doc:name="Update Product">
<db:parameterized-query><![CDATA[update product set name = #[payload.name], description = #[payload.description], product_number = #[payload.productNumber], manufactured = #[payload.manufactured], colors = #[payload.colors], categories= #[payload.categories], stock = #[payload.stock], safety_stock_level = #[payload.safetyStockLevel], standard_cost = #[payload.standardCost], list_price = #[payload.listPrice], size = #[payload.size], size_unit_measure_code = #[payload.sizeUnitMeasureCode], weight = #[payload.weight], weight_unit_measure_code = #[payload.weightUnitMeasureCode], days_to_manufacture = #[payload.daysToManufacture], images = #[payload.images], modified_date = CURDATE() where id = #[id]]]></db:parameterized-query>
</db:update>
</transactional>
<set-payload value="#[NullPayload.getInstance()]" doc:name="Set Payload" />
<set-property propertyName="http.status" value="204" doc:name="Set Status" />
</flow>
There are no changes regarding put-product-flow definition.
<flow name="put-product-flow" />
Create a put-json-product-to-java.dwl file under src/main/resources/mappings folder and migrate the original script from DW 1.0 to 2.0.
%dw 1.0
%output application/java
---
{
categories: payload.categories joinBy ",",
colors: payload.colors joinBy ",",
daysToManufacture: payload.daysToManufacture,
description: payload.description,
images: payload.images joinBy ",",
listPrice: payload.listPrice,
manufactured: payload.manufactured,
name: payload.name,
productNumber: payload.productNumber,
safetyStockLevel: payload.safetyStockLevel,
size: payload.size,
sizeUnitMeasureCode: payload.sizeUnitMeasureCode,
standardCost: payload.standardCost,
stock: payload.stock,
weight: payload.weight,
weightUnitMeasureCode: payload.weightUnitMeasureCode
}
%dw 2.0
output application/java
---
{
categories: payload.categories joinBy ",",
colors: payload.colors joinBy ",",
daysToManufacture: payload.daysToManufacture,
description: payload.description,
images: payload.images joinBy ",",
listPrice: payload.listPrice,
manufactured: payload.manufactured,
name: payload.name,
productNumber: payload.productNumber,
safetyStockLevel: payload.safetyStockLevel,
size: payload.size,
sizeUnitMeasureCode: payload.sizeUnitMeasureCode,
standardCost: payload.standardCost,
stock: payload.stock,
weight: payload.weight,
weightUnitMeasureCode: payload.weightUnitMeasureCode
}
Add a Transform component that sets updatePayload using the previously created DataWeave transformation and id with variables/set-productId-variable.dwl script.
<flow name="put-product-flow">
<ee:transform doc:name="JSon to Product">
<ee:message />
<ee:variables >
<ee:set-variable variableName="updatePayload" resource="mappings/put-json-product-to-java.dwl" />
<ee:set-variable variableName="id" resource="variables/set-productId-variable.dwl" />
</ee:variables>
</ee:transform>
</flow>
For configuring the details of the transaction, replace the Mule 3.x transactional scope with the new try scope and set the transactionalAction attribute to ALWAYS_BEGIN.
<try doc:name="Try" transactionalAction="ALWAYS_BEGIN">
</try>
Add a db:update element into the Try scope, referencing the MySQL Global Configuration. Parameters must be supplied as key-value pairs into the db:input-parameters element.
<try doc:name="Try" transactionalAction="ALWAYS_BEGIN">
<db:update config-ref="MySQL_Configuration" doc:name="Update Product">
<db:sql >update product
set name = :name, description = :description, product_number = :product_number, manufactured = :manufactured, colors = :colors, categories= :categories, stock = :stock, safety_stock_level = :safety_stock_level, standard_cost = :standard_cost, list_price = :list_price, size = :size, size_unit_measure_code = :size_unit_measure_code, weight = :weight, weight_unit_measure_code = :weight_unit_measure_code, days_to_manufacture = :days_to_manufacture, images = :images, modified_date = CURDATE()
where id = :id</db:sql>
<db:input-parameters ><![CDATA[#[{'name': vars.updatePayload.name, 'description': vars.updatePayload.description, 'product_number': vars.updatePayload.productNumber, 'manufactured': vars.updatePayload.manufactured, 'colors': vars.updatePayload.colors, 'categories': vars.updatePayload.categories, 'stock': vars.updatePayload.stock, 'safety_stock_level': vars.updatePayload.safetyStockLevel, 'standard_cost': vars.updatePayload.standardCost, 'list_price': vars.updatePayload.listPrice, 'size': vars.updatePayload.size, 'size_unit_measure_code': vars.updatePayload.sizeUnitMeasureCode, 'weight': vars.updatePayload.weight, 'weight_unit_measure_code': vars.updatePayload.weightUnitMeasureCode, 'days_to_manufacture': vars.updatePayload.daysToManufacture, 'images': vars.updatePayload.images, 'id': vars.id}]]]></db:input-parameters>
</db:update>
</try>
Add a Transform component and set the httpStatus variable with 204 using set-httpStatus-with-204.dwl file into src/main/resources/variables.
<ee:transform doc:name="Set Status">
<ee:message />
<ee:variables >
<ee:set-variable variableName="httpStatus" resource="variables/set-httpStatus-with-204.dwl" />
</ee:variables>
</ee:transform>
<flow name="put-product-flow">
<ee:transform doc:name="JSon to Product">
<ee:message />
<ee:variables >
<ee:set-variable variableName="updatePayload" resource="mappings/put-json-product-to-java.dwl" />
<ee:set-variable variableName="id" resource="variables/set-productId-variable.dwl" />
</ee:variables>
</ee:transform>
<try doc:name="Try" transactionalAction="ALWAYS_BEGIN">
<db:update config-ref="MySQL_Configuration" doc:name="Update Product">
<db:sql >update product
set name = :name, description = :description, product_number = :product_number, manufactured = :manufactured, colors = :colors, categories= :categories, stock = :stock, safety_stock_level = :safety_stock_level, standard_cost = :standard_cost, list_price = :list_price, size = :size, size_unit_measure_code = :size_unit_measure_code, weight = :weight, weight_unit_measure_code = :weight_unit_measure_code, days_to_manufacture = :days_to_manufacture, images = :images, modified_date = CURDATE()
where id = :id</db:sql>
<db:input-parameters ><![CDATA[#[{'name': vars.updatePayload.name, 'description': vars.updatePayload.description, 'product_number': vars.updatePayload.productNumber, 'manufactured': vars.updatePayload.manufactured, 'colors': vars.updatePayload.colors, 'categories': vars.updatePayload.categories, 'stock': vars.updatePayload.stock, 'safety_stock_level': vars.updatePayload.safetyStockLevel, 'standard_cost': vars.updatePayload.standardCost, 'list_price': vars.updatePayload.listPrice, 'size': vars.updatePayload.size, 'size_unit_measure_code': vars.updatePayload.sizeUnitMeasureCode, 'weight': vars.updatePayload.weight, 'weight_unit_measure_code': vars.updatePayload.weightUnitMeasureCode, 'days_to_manufacture': vars.updatePayload.daysToManufacture, 'images': vars.updatePayload.images, 'id': vars.id}]]]></db:input-parameters>
</db:update>
</try>
<ee:transform doc:name="Set Status">
<ee:message />
<ee:variables >
<ee:set-variable variableName="httpStatus" resource="variables/set-httpStatus-with-204.dwl" />
</ee:variables>
</ee:transform>
</flow>
delete-product-flow deletes the product record from the MySQL database with the id specified as a URI parameter returning an HTTP 204 status code.
<flow name="delete-product-flow">
<transactional action="ALWAYS_BEGIN" doc:name="Transactional">
<db:delete config-ref="MySQL_Configuration" doc:name="Delete Product">
<db:parameterized-query><![CDATA[delete from product where id=#[id]]]></db:parameterized-query>
</db:delete>
</transactional>
<set-payload value="#[NullPayload.getInstance()]" doc:name="Set Payload"/>
<set-property propertyName="http.status" value="204" doc:name="Set Status"/>
</flow>
There are no changes regarding delete-product-flow definition.
<flow name="delete-product-flow" />
Add a Transform component that references the DW script that sets a variable with the productId value received as a URI parameter.
<flow name="delete-product-flow">
<ee:transform doc:name="Set productId variable">
<ee:message />
<ee:variables>
<ee:set-variable variableName="productId" resource="variables/set-productId-variable.dwl" />
</ee:variables>
</ee:transform>
</flow>
For configuring the details of the transaction, replace the Mule 3.x transactional scope with the new try scope and set the transactionalAction attribute to ALWAYS_BEGIN.
<try doc:name="Try" transactionalAction="ALWAYS_BEGIN">
</try>
Add a db:delete element into the Try scope, referencing the MySQL Global Configuration. Parameters must be supplied as key-value pairs into the db:input-parameters element. Opposite to Mule 3.x, the previously defined productId flow variable must be accessed as vars.productId instead of flowVars.productId.
<try doc:name="Try" transactionalAction="ALWAYS_BEGIN">
<db:delete config-ref="MySQL_Configuration" doc:name="Delete Product">
<db:sql>delete from product where id=:productId</db:sql>
<db:input-parameters><![CDATA[#[{'productId' : vars.productId}]]]></db:input-parameters>
</db:delete>
</try>
Create a set-httpStatus-with-204.dwl file under src/main/resources/variables as follows.
%dw 2.0 output application/java --- 204
Set httpStatus variable with the value 204 using a Transform Component for defining a NO CONTENT response code.
<ee:transform doc:name="Set 204 HTTP Status code">
<ee:message />
<ee:variables>
<ee:set-variable variableName="httpStatus" resource="variables/set-httpStatus-with-204.dwl" />
</ee:variables>
</ee:transform>
To return a specific HTTP Status code, instead of setting a http.status property, APIkit in Mule 4 requires setting a variable with the name httpStatus.
<flow name="delete-product-flow">
<ee:transform doc:name="Set productId variable">
<ee:message />
<ee:variables>
<ee:set-variable variableName="productId" resource="variables/set-productId-variable.dwl" />
</ee:variables>
</ee:transform>
<try doc:name="Try" transactionalAction="ALWAYS_BEGIN">
<db:delete config-ref="MySQL_Configuration" doc:name="Delete Product">
<db:sql>delete from product where id=:productId</db:sql>
<db:input-parameters><![CDATA[#[{'productId' : vars.productId}]]]></db:input-parameters>
</db:delete>
</try>
<ee:transform doc:name="Set 204 HTTP Status code">
<ee:message />
<ee:variables>
<ee:set-variable variableName="httpStatus" resource="variables/set-httpStatus-with-204.dwl" />
</ee:variables>
</ee:transform>
</flow>
To migrate backend flows:
For each Backend Flow generated by APIkit, add a flow-ref to its implementation.
+ .Configuration XML for Backend Flows in Studio 6.
<flow name="get:/product:api-config">
<flow-ref name="get-products-flow" doc:name="get-products-flow" />
</flow>
<flow name="get:/product/{id}:api-config">
<flow-ref name="get-product-by-id-flow" doc:name="get-product-by-id-flow" />
</flow>
<flow name="post:/product:application/json:api-config">
<flow-ref name="post-product-flow" doc:name="post-product-flow" />
</flow>
<flow name="delete:/product/{id}:api-config">
<flow-ref name="delete-product-flow" doc:name="delete-product-flow" />
</flow>
<flow name="put:/product/{id}:application/json:api-config">
<flow-ref name="put-product-flow" doc:name="put-product-flow" />
</flow>
+
+ .Configuration XML for Backend Flows in Studio 7.
<flow name="get:\product:api-product-config">
<flow-ref name="get-products-flow" doc:name="get-products-flow" />
</flow>
<flow name="get:\product\(id):api-product-config">
<flow-ref doc:name="get-product-by-id-flow" name="get-product-by-id-flow"/>
</flow>
<flow name="post:\product:application\json:api-product-config">
<flow-ref name="post-product-flow" doc:name="post-product-flow" />
</flow>
<flow name="delete:\product\(id):application\json:api-product-config">
<flow-ref doc:name="delete-product-flow" doc:id="38894873-9a01-4e59-8362-5eefce5ea043" name="delete-product-flow"/>
</flow>
<flow name="put:\product\(id):application\json:api-product-config">
<flow-ref doc:name="put-product-flow" doc:id="e64cf378-26a2-4435-8d0d-269c50282b3c" name="put-product-flow"/>
</flow>
Add APP:NOT_FOUND exception type to the ApiKit-generated 404 mapping to handle the exception thrown by the validation:is-true element in get-product-by-id-flow. You can repeat this process for any other errors you want to map to a specific status code.
<apikit:mapping statusCode="404">
<apikit:exception value="org.mule.module.apikit.exception.NotFoundException" />
<set-property propertyName="Content-Type" value="application/json" doc:name="Property" />
<set-payload value="{ "message": "Resource not found" }" doc:name="Set Payload" />
</apikit:mapping>
<on-error-propagate type="APIKIT:NOT_FOUND, APP:NOT_FOUND" doc:name="On Error Propagate" enableNotifications="true" logException="true">
<ee:transform xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd" doc:id="70f893cb-6106-42d9-9a95-e201e9349159">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{message: "Resource not found"}]]></ee:set-payload>
</ee:message>
<ee:variables>
<ee:set-variable variableName="httpStatus"><![CDATA[404]]></ee:set-variable>
</ee:variables>
</ee:transform>
</on-error-propagate>
Add a Generic ApiKit Mapping 500 for all the non previously managed exceptions.
<apikit:mapping-exception-strategy name="api-apiKitGlobalExceptionMapping">
<!-- other exception strategies here -->
<apikit:mapping statusCode="500">
<apikit:exception value="java.lang.Exception" />
<set-property propertyName="Content-Type" value="application/json" doc:name="Property" />
<set-payload value="{ "message": "Internal Server Error" }" doc:name="Set Payload" />
</apikit:mapping>
</apikit:mapping-exception-strategy>
<error-handler>
<!-- other exception strategies here -->
<on-error-propagate doc:name="On Error Propagate" enableNotifications="true" logException="true">
<ee:transform xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd" doc:id="7e8049ff-cae6-4937-8569-c36bb7f06dad">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{message: "Internal Server Error"}]]></ee:set-payload>
</ee:message>
<ee:variables>
<ee:set-variable variableName="httpStatus"><![CDATA[500]]></ee:set-variable>
</ee:variables>
</ee:transform>
</on-error-propagate>
</error-handler>